电商或者内容类APP中,H5一般都会占据一席之地,Native跟H5通讯会必不可少,好比某些场景H5通知native去分享,native通知H5局部刷新等,Android自己也提供这样的接口,好比addJavascriptInterface、loadUrl("javascript:..."),而须要支持的能力也要是双工的。javascript
实现这种机制的方式并不惟一,但使用不当常常会引入不少问题,好比:H5同Native须要一个中间js文件,实现简单的通讯协议,这个js文件有的产品作法是让前端本身加载,有的作法是客户端注入,也就是经过loadUrl("javascript:...")注入。采用客户端注入这种方式就多少有问题,由于没有一个很合适的时机既保证注入成功,又保证注入及时。若是在onPageStarted时注入,不少手机会注入失败,若是onPageFinished时注入,又太迟,致使不少功能打折扣。再好比:有些人经过prompt方式实现H5通知Native,而prompt是一个可能产生问题的同步方法,一旦没法返回,整个js环境就会挂掉,致使全部H5页面都没法打开,下面简单说下两种实现,一是经过addJavascriptInterface,另外一种就 是经过prompt。前端
WebView的addJavascriptInterface方法容许Natvive向Web页面注入Java对象,以后,在js中即可以直接访问该对象,使用@JavascriptInterface注解的方法。好比经过以下代码向前端注入一个名字为mJsMethodApi的java对象java
class JsMethodApi {
/**
* js调用native,可能须要回调
*/
@JavascriptInterface
public void callNative(String jsonString) {
...
}
}
webView.addJavascriptInterface(new JsMethodApi(), "mJsMethodApi");
复制代码
在前端的js代码中,是能够直接经过mJsMethodApi.callNative(jsonString)通知Native的,并且经过addJavascriptInterface注入的对象在H5的任何地方均可以调用,不存在注入时机跟注入失败的问题,在H5的head里调用都没问题。git
<head>
<script type="text/javascript" >
JsMethodApi.callNative('头部就能够回调');
</script>
</head>
复制代码
经测试,实际上是能够通知到Native的,不过有一点须要注意callNative是这JavaBridge这个线程中执行的,虽然不提清楚它跟JS线程的关系,但JS会阻塞等待callNative函数执行完毕再往下走,因此 @JavascriptInterface注解的方法里面最好也不要作耗时操做,最好利用Handler封装一下,让每一个任务本身处理,耗时的话就开线程本身处理。github
若是前端通知Native时须要回调怎么办?能够抽离到一个中间的js,为每一个任务设置一个ID,暂存回调函数,等到Native处理结束后,先走这个中间的js,找到对应的js回调函数执行便可,web
var _callbacks = {};
function callNative(method, params, success_cb, error_cb) {
var request = {
version: jsRPCVer,
method: method,
params: params,
id: _current_id++
};
<!--暂存回调函数-->
if (typeof success_cb !== 'undefined') {
_callbacks[request.id] = {
success_cb: success_cb,
error_cb: error_cb
};
}
<!--利用JsMethodApi通知Native-->
JsMethodApi.callNative(JSON.stringify(request));
};
复制代码
以上js代码完成回调的暂存、通知native执行,native那边会收到js消息,同时里面包含着id,等到native执行完毕后,将执行结果与消息id通知到这个中间层js,找到对应的回调函数执行便可,以下:chrome
jsRPC.onJsCallFinished = function(message) {
var response = message;
<!--找到回调函数-->
var success_cb = _callbacks[response.id].success_cb;
<!--删除-->
delete _callbacks[response.id];
<!--执行回调函数-->
success_cb(response.result);
};
复制代码
这样就完成H5通知Native,同时Native将结果回传给H5,并完成回调这样一条通路。Native通知H5,这条路怎么办?流程大概相似,一样能够基于一个消息ID完成回调,不过更加灵活,由于Native通知前端的接口不太好统一,具体使用本身把握。json
参考工程 https://github.com/happylishang/CMJsBridge 安全
注意不要混淆多线程
若是混淆了,@JavascriptInterface注解的方法可能就没了,结果是,JS就没办法知己调用对应的方法,致使通讯失败。
关于漏洞问题
4.2之后,WebView会禁止JS调用没有添加@JavascriptInterface方法, 解决了安全漏洞,并且不多APP兼容到4.2之前,安全问题能够忽略。
关于阻塞问题
JavascriptInterface注入的方法被js调用时,能够看作是一个同步调用,虽然二者位于不一样线程,可是应该存在一个等待通知的机制来保证,因此Native中被回调的方法里尽可能不要处理耗时操做,不然js会阻塞等待较长时间,以下图
平常使用Webview的时候通常都会设置WebChromeClient,用来处理一些进度、title之类的事件,除此以外,WebChromeClient还提供了几个js回调的入口,如onJsPrompt,onJsAlert等,在前端调用window.alert,window.confirm,window.prompt时,
public boolean onJsAlert(WebView view, String url, String message,
JsResult result) {
return false;
}
public boolean onJsConfirm(WebView view, String url, String message,
JsResult result) {
return false;
}
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
return false;
}
复制代码
在js调用window.alert,window.confirm,window.prompt时,会调用WebChromeClient对应方法,能够此为入口,做为消息传递通道,考虑到开发习惯,通常不会选择alert跟confirm,一般会选promopt做为入口,在App中就是onJsPrompt做为jsbridge的调用入口。因为onJsPrompt是在UI线程执行,因此尽可能不要作耗时操做,能够借助Handler灵活处理。对于回调的处理跟上面的addJavascriptInterface的方式同样便可,采用消息ID方式作暂存区分,区别就是这里采用 prompt(JSON.stringify(request));通知native,以下:
function callNative(method, params, success_cb, error_cb) {
var request = {
version: jsRPCVer,
method: method,
params: params,
id: _current_id++
};
if (typeof success_cb !== 'undefined') {
_callbacks[request.id] = {
success_cb: success_cb,
error_cb: error_cb
};
}
prompt(JSON.stringify(request));
};
复制代码
同以前JavaBridge线程相似,这里prompt的js线程必需要等待UI线程中onJsPrompt返回才会唤醒,能够认为是个同步阻塞调用(应该是经过线程等待来作的)。
public class JsWebChromeClient extends WebChromeClient {
JsBridgeApi mJsBridgeApi;
public JsWebChromeClient(JsBridgeApi jsBridgeApi) {
mJsBridgeApi = jsBridgeApi;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
try {
if (mJsBridgeApi.handleJsCall(message)) {
<!--若是睡眠10s js就会等待10s-->
// Thread.sleep(10000);
result.confirm("sdf");
return true;
}
} catch (Exception e) {
return true;
}
// 未处理走默认逻辑
return super.onJsPrompt(view, url, message, defaultValue, result);
}
}
复制代码
若是在onJsPrompt睡眠10s,js的prompt函数必定会阻塞等待10s才返回,这个设计就要求咱们不能在onJsPrompt中作耗时操做,systrace中能够验证。
上图中,chrome_iothread看作js线程。
从表现上来看,onJsPrompt必须执行完毕,prompt函数才会返回,不然js线程会一直阻塞在这里。实际使用中确实会发生这种状况,尤为是APP中有不少线程的场景下,怀疑是这么一种场景:
若是不主动destroy webview,能够很大程度避免这个问题,具体Chrome的实现如何,还没分析过,这里只是根据现象推测如此。而WebView.addJavascriptInterface并不会有这个问题,不管是否主动destroy Webview,都不会上述问题,可能chrome对addJavascriptInterface这种方式作了额外处理,在本身销毁的时候,主动唤起JS线程,可是onJsPrompt所在的UI线程显然没处理这种场景。
参考工程 https://github.com/happylishang/CMJsBridge
做者:看书的小蜗牛 原文连接:Android 混合开发之JsBridge
仅供参考,欢迎指正