Webview 和 Native 通信总结

作为跨平台的解决方案之一,使用 webview 这种 hybrid 方式是最早被应用起来的,现在也已慢慢走向成熟。本文简单介绍一下使用 webview 过程中 js 和 native 的几种通信方式和性能对比。

通信方式介绍

Android webview 关于 Js 和 Native 的几种通信方式如下所示:

webview-native通信方式

Native-Js

webview.loadUrl

Java 中调用 Js 代码,使用 loadUrl() 比较简单,如下所示条用弹出一个提示框:

1
webView.loadUrl("javascript:alert('hello')");

Js 代码统一在页面加载完成,即 onPageFinished() 之后调用。

webview.evaluateJavaScript

在 Android 4.4(API ≥ 19)后,新增了在 Native 中调用 Js 代码的方式:

1
2
3
4
5
6
7
webView.evaluateJavascript("(function() { return 'evaluateJavascript'; })()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.d(TAG, "onReceiveValue:" + value);
}
});
}

这种方式优点:

  • 执行效率高
  • 有 Js 层执行后的回调

但要注意的是,这种方式不会触发页面的刷新(loadUrl()加载页面时会触发刷新)。

Js-Native

addJavaScriptInterface

js 调用 native 效率最高的是使用 addJavaScriptInterface 方式,获取注入在 Js 中的 Java 对象,直接调用它的成员方法。

在 Java 层中只需要创建一个对象,并声明给 Js 层调用的方法,再添加即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public void registerJSInterface(WebView webView) {
Log.d(TAG, "registerJSInterface");
webView.addJavascriptInterface(new WebViewJSInterface(), "mWebViewJSInterface");
}
public class WebViewJSInterface{
@JavascriptInterface
public void invoke(String url) {
Log.d(TAG, "invoke url:" + url);
}
}

Js 层调用:

1
mWebViewJSInterface.invoke('hello')

需要注意的是:addJavascriptInterface 的执行时机是,页面的下一次 load 时。所以一般可以将它放在页面加载之前进行注册。

shouldOverrideUrlLoading

Js 层可以通过 a 标签的跳转和 HTML 的请求有相关的响应,可以被 WebViewClient 的回调方法 shouldOverrideUrlLoading () 拦截。这里我们可以利用 scheme iframe 机制,来实现类 shouldOverrideUrlLoading 的请求。

1
2
3
4
5
6
7
var url = 'jsbridge://apollo/isApolloEngineReady?p=%7B%22callback%22%3A%22__MQQ_CALLBACK_AUTO_54%22%7D#54';
var iframe = document.createElement('iframe');
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(function() {
iframe.remove();
}, 100);

再在 Java 层给 webView 设置 WebViewClient 的回调,实现 shouldOverrideUrlLoading 拦截:

1
2
3
4
5
6
7
mWebView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d(TAG, "shouldOverrideUrlLoading url:" + url);
return super.shouldOverrideUrlLoading(view, url);
}
});

使用 loadUrl()window.location 第一次加载页面时,shouldOverrideUrlLoading 是不会接收到回调的,但之后的每次页面请求都可以拦截到,可以用来判断是否为重定向。

onJsPromt、onJsAlert、onJsConfirm/onConsoleMessage

在 Android 中, WebChromeClient 回调会处理 Js 层调用 window 对象的 alert,confirm,prompt,console 方法,可以在拦截时进行定制化 UI 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public WebChromeClient mWebChromeClient = new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
Log.d(TAG, "onJsAlert url:" + url + ",message:" + message);
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Log.d(TAG, "onConsoleMessage consoleMessage:" + consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
};

性能对比

综合以上 Js - Native 的通信方式,对比如下:

方案 性能 适用范围 有无返回值
addJavaScriptInterface 高,直接调用native方法,无需模式匹配 4.2及以上版本 可以返回任意值
shouldOverrideUrlLoading 低,通过iframe或者script方式发起url请求 兼容性好 无返回值
onJsPromt/console.log 较快 兼容性一般 返回值只有true or false
webview.loadUrl() 慢,会刷新触发新页面 所有版本
webview.evaluateJavaScript() 4.4以上版本 有返回值

对于 Native 处理后想给 Js 回调:

  • addJavaScriptInterface 或 onJsPromt/console.log 这俩种方式有同步直接返回值,shouldOverrideUrlLoading 是不能直接返回的。当然为了保持前端抒写一致性,这里需要统一处理异步回调。

  • Js 传入 Url 参数时就传入唯一的自增的 callbackId 方法的名字,Native 层处理后再通过 loadUrl()evaluateJavascript() 通过 window 对象调用全局监听的 callbackId 方法,以此完成异步回调。