AndreGeng / AndreGeng.github.io

blog repository
1 stars 0 forks source link

JSBridge实现原理 #25

Open AndreGeng opened 5 years ago

AndreGeng commented 5 years ago

JSBridge主要是给javascript和Native相互通信的接口。

javascript调用native

实现方式主要有以下三种:

public class WebAppInterface { Context mContext;

// Instantiate the interface and set the context
WebAppInterface(Context c) {
    mContext = c;
}

// Show a toast from the web page
@JavascriptInterface
public void showToast(String toast) {
    Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}

@JavascriptInterface
public int getAndroidVersion() {
    return android.os.Build.VERSION.SDK_INT;
}

@JavascriptInterface
public void showAndroidVersion(String versionName) {
    Toast.makeText(mContext, versionName, Toast.LENGTH_SHORT).show();
}

}

import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.webkit.JsResult; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient;

public class WebViewActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_webview);

    WebView webView = (WebView) findViewById(R.id.webview);
    webView.loadUrl("file:///android_asset/index.html");

    webView.addJavascriptInterface(new WebAppInterface(this), "AndroidInterface"); // To call methods in Android from using js in the html, AndroidInterface.showToast, AndroidInterface.getAndroidVersion etc
    WebSettings webSettings = webView.getSettings();
    webSettings.setJavaScriptEnabled(true);
    webView.setWebViewClient(new MyWebViewClient());
    webView.setWebChromeClient(new MyWebChromeClient());
}

private class MyWebViewClient extends WebViewClient {
    @Override
    public void onPageFinished (WebView view, String url) {
        //Calling a javascript function in html page
        view.loadUrl("javascript:alert(showVersion('called by Android'))");
    }
}

private class MyWebChromeClient extends WebChromeClient {
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        Log.d("LogTag", message);
        result.confirm();
        return true;
    }
}

}

js调用方式

window.nativeBridge.postMessage(message);

在 4.2 之前,Android 注入 JavaScript 对象的接口是 addJavascriptInterface,但是这个接口有漏洞,可以被不法分子利用,危害用户的安全,因此在 4.2 中引入新的接口 @JavascriptInterface(上面代码中使用的)来替代这个接口,解决安全问题。所以 Android 注入对对象的方式是 有兼容性问题的。

1.2 URL劫持
URL劫持主要是使用 shouldOverrideUrlLoading() 进行 WebView URL 劫持。从方法名可以看出,它是 WebView 拦截 URL 的一种回调,当 WebView 发生 URL 跳转的时候会触发该回调。在该回调中我们能够获取到前端提供的 URL 地址。我们通过构造约定协议的 URL 地址提供给客户端识别,识别成功后执行对应的方法即可。

WebView webview = (WebView) findViewById(R.id.webview); webview.loadUrl('http://imnerd.org'); webview.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if(url.equals('sdk:hello')) { System.out.println('hello world'); return true; } return super.shouldOverrideUrlLoading(view, url); } });

1.3 方法劫持
同 URL 劫持类似,方法劫持主要是利用 JS 的一些方法执行时会触发 Android 客户端中的一些回调,通过对前端参数进行识别来执行对应的客户端代码。目前前端主要有以下四种方法会触发对应的回调方法,对应关系如下:

| JS方法 | 客户端回调 |
|-------------|------------------|
| alert | onJsAlert |
| prompt | onJsPrompt |
| confirm | onJsConfirm |
| console.log | onConsoleMessage |

将这四个方法列在一块是因为这几个方法的本质上都是差不多,定义好对应的回调方法即可。客户端具体的配置如下:

//定义好劫持回调类 private class hijackWebChromeClient extends WebChromeClient {
public boolean hijack(String text) { if(text.equals('sdk:hello')) { System.out.println('hello world'); return true; } return false; } @Override public boolean onJsPrompt(WebView view, String message, String defaultValue, JSPromptResult result) { if(this.hijack(message)) { return true; } return super.onJsPrompt(view, url, message, defaultValue, result); } @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { if(this.hijack(message)) { return true; }

return super.onJsAlert(view, url, message, result);

} @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { if(this.hijack(message)) { return true; }

return super.onJsConfirm(view, url, message, result);

} @Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
String message = consoleMessage.message(); if(this.hijack(message)) { return true; }

return super.onConsoleMessage(consoleMessage);  

}
@Override
public void onConsoleMessage(String message, int lineNumber, String sourceID) { if(this.hijack(message)) { return true; } super.onConsoleMessage(message, lineNumber, sourceID);
}
} //注入劫持回调类 WebView webview = (WebView) findViewById(R.id.webview); webview.loadUrl('http://imnerd.org'); webview.setWebChromeClient(new hijackChromeClient);

# native调用javascript
相比于 JavaScript 调用 Native, Native 调用 JavaScript 较为简单,毕竟不管是 iOS 的 UIWebView 还是 WKWebView,还是 Android 的 WebView 组件,都以子组件的形式存在于 View/Activity 中,直接调用相应的 API 即可。

Native 调用 JavaScript,其实就是执行拼接 JavaScript 字符串,从外部调用 JavaScript 中的方法,因此 JavaScript 的方法必须在全局的 window 上。(闭包里的方法,JavaScript 自己都调用不了,更不用想让 Native 去调用了)
同样以android为例,
对于 Android,在 Kitkat(4.4)之前并没有提供 iOS 类似的调用方式,只能用 loadUrl 一段 JavaScript 代码,来实现:

webView.loadUrl("javascript:" + javaScriptString);

而 Kitkat 之后的版本,也可以用 evaluateJavascript 方法实现:

webView.evaluateJavascript(javaScriptString, new ValueCallback() { @Override public void onReceiveValue(String value) {

}

});


【注】:使用 loadUrl 的方式,并不能获取 JavaScript 执行后的结果。
AndreGeng commented 5 years ago

reference: 混合开发中的jsbridge Javascript Interface for Android and Javascript communication 前端 WEBVIEW 指南之 ANDROID 交互篇