電商或者內容類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
僅供參考,歡迎指正