衆所周知,app的一些功能可能會使用到H5開發,這就難免會遇到java與js的互相調用,像android利用WebViewJavascriptBridge實作js和java的互動,這裡主要介紹下JsBridge的原理和使用。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SZjVzY5kzN0YTMxYWZ4gzNjlDZ0E2NwEjMjdTNxYjM28CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
一、什麼是JSBridge
JSBridge主要是給JavaScript提供調用Native功能的接口,讓混合開發中的前端部分可以友善地使用Native的功能(例如:位址位置、攝像頭)。而且JSBridge的功能不止調用Native功能這麼簡單寬泛。
JSBridge是一座用JavaScript搭建起來的橋,一端是Web,一端是Native,是Native和Web之間的橋梁。我們搭建這座橋的目的也很簡單,讓Native可以調用Web的js代碼,讓Web可以“調用”原生的代碼。它的核心是建構Native和Web間消息通信的通道,而且這個通信的通道是雙向的。
雙向通信的通道:JS向Native發送消息:調用相關功能、通知Native目前JS的相關狀态等。Native向JS發送消息:回溯調用結果、消息推送、通知JS目前Native的狀态等。
H5與Native互動如下圖:
二、JSBridge的實作原理
JavaScript是運作在一個單獨的JS Context中(例如WebView的Webkit引擎、JSCore)。由于這些Context與原生運作環境的天然隔離,我們可以将這種情況與RPC(Remote Procedure Call,遠端過程調用)通信進行類比,将Native與JavaScript的每次互相調用看做一次RPC調用。
在JSBridge的設計中,可以把前端看做RPC的用戶端,把Native端看做RPC的伺服器端,進而JSBridge要實作的主要邏輯就出現了:通信調用(Native與JS通信)和句柄解析調用。
三、JSBridge的通信原理
1.JavaScript調用Native的方式
主要有兩種:注入API和攔截URL SCHEME
1.1 注入API
注入API方式的主要原理是,通過WebView提供的接口,向JavaScript的Context(window)中注入對象或者方法,讓JavaScript調用時,直接執行相應的Native代碼邏輯,達到JavaScript調用Native的目的。
iOS
UIWebVIew(iOS2+)和WKWebView(iOS8+)的調用方式有所差別
//假設ios用戶端約定方法名為nativeBridge//UIWebViewwindow.nativeBridge(message);//WKWebViewwindow.webkit.messageHandlers.nativeBridge.postMessage(message);
Android
原理:通過WebView提供的addJavascriptInterface方法給浏覽器window注入一個命名空間,然後給Web增加一些可以操作Java的反射。
//addJavascriptInterfacemWebView.addJavascriptInterface(new Class(), 'android'); //@JavascriptInterfacepublic class Class(){ @JavascriptInterface public void method(){ }}//js 代碼window.android.method();
備注:在4.2之前,Android注入JavaScript對象的接口是addJavascriptInterface,但是這個接口有漏洞,可以被不法分子利用,危害使用者的安全,是以在4.2中引入新的接口@JavascriptInterface(上面代碼中使用的)來替代這個接口,解決安全問題。
1.2 攔截URL SCHEME
解釋一下URL SCHEME:URL SCHEME是一種類似于url的連結,是為了友善app直接互相調用設計的,形式和普通的url近似,主要差別是protocol和host一般是自定義的。
例如:esign://home/url?url=www.baidu.com,protocol是esign,host則是home。
攔截URL SCHEME 的主要流程是:Web端通過某種方式(例如iframe.src)發送URLScheme請求,之後Native攔截到請求并根據URL SCHEME(包括所帶的參數)進行相關操作。
在時間過程中,這種方式有一定的缺陷:
- 使用iframe.src發送URLSCHEME會有url長度的隐患。
為什麼選擇iframe.src不選擇locaiton.href?因為如果通過location.href連續調用Native,很容易丢失一些調用。
建立請求,需要一定的耗時,比注入API的方式調用同樣的功能,耗時會較長。
是以:JavaScript調用Native推薦使用注入API的方式
2.Native調用JavaScript的方式
相比于JavaScript調用Native,Native調用JavaScript較為簡單,直接執行拼接好的JavaScript代碼即可。
從外部調用JavaScript中的方法,是以JavaScript的方法必須在全局的window上。
iOS
對于iOS的UIWebView,示例如下:
//UIWebViewresult = [uiWebview stringByEvaluatingJavaScriptFromString:javaScriptString];
對于iOS的WKWebView,示例如下:
//WKWebView[wkWebView evaluateJavaScript:javaScriptString completionHandler:completionHandler];
Android
Android
在Kitkat(4.4)之前是使用webview的loadUrl進行調用的:
webView.loadUrl("javascript:JSBridge.trigger('webviewReady')");
而Kitkat之後的版本,也可以用evaluateJavascript方法實作:
webView.evaluateJavascript(javaScriptString,new ValueCallback() { @Override publicvoidonReceiveValue(String value){ }});
四、JSBridge 接口實作
從上面的剖析中,可以得知,JSBridge的接口主要功能有兩個:調用Native(給Native發消息)和接被Native調用(接收Native消息)。是以,JSBridge可以設計如下:
window.JSBridge = { // 調用 Native invoke: function(msg) { // 判斷環境,擷取不同的 nativeBridge nativeBridge.postMessage(msg); }, receiveMessage: function(msg) { // 處理 msg }};
在上面部分中,提到過RPC中有一個非常重要的環節是句柄解析調用,這點在JSBridge中展現為句柄與功能對應關系。同時,我們将句柄抽象為橋名(BridgeName),最終演化為一個BridgeName對應一個Native功能或者一類Native消息。基于此點,JSBridge的實作可以優化為如下:
window.JSBridge = { // 調用 Native invoke: function(bridgeName, data) { // 判斷環境,擷取不同的 nativeBridge nativeBridge.postMessage({ bridgeName: bridgeName, data: data || {} }); }, receiveMessage: function(msg) { var bridgeName = msg.bridgeName, data = msg.data || {}; //具體邏輯 }};
終極提問:消息都是單向的,那麼調用Native功能時Callback怎麼實作的?
對于JSBridge的Callback,其實就是RPC架構的回調機制。當然也可以用更簡單的JSONP機制解釋:
當發送JSONP請求時,url參數裡會有callback參數,其值是目前頁面唯一的,而同時以此參數值為key将回調函數存到window上,随後,伺服器傳回script中,也會以此參數值作為句柄,調用相應的回調函數。
整體流程:
在Native端配合實作JSBridge的JavaScript調用Native邏輯也很簡單,主要的代碼邏輯是:接收到JavaScript消息=>解析參數,拿到bridgeName、data和callbackId=>根據bridgeName找到功能方法,以data為參數執行=>執行傳回值和callbackId一起回傳前端。
Native調用JavaScript也同樣簡單,直接自動生成一個唯一的ResponseId,并存儲句柄,然後和data一起發送給前端即可。
五、JSBridge的總結
對于JSBridge的引用,常用有如下兩種方式,但各有利弊。
1.由Native端進行注入
注入方式和Native調用JavaScript類似,直接執行橋的全部代碼。
它的優點是:
橋的版本很容易與Native保持一緻,Native端不用對不同版本的JSBridge進行相容。
它的缺點是:
注入時機不确定,需要實作注入失敗後重試的機制,保證注入的成功率,同時JavaScript端在調用接口時,需要優先判斷JSBridge是否已經注入成功。
2.由JavaScript端引用
直接與JavaScript一起執行。
它的優點是:
JavaScript端可以确定JSBridge的存在,直接調用即可。
它的缺點是:
如果橋的實作方式有更改,JSBridge需要相容多版本的Native Bridge或者Native Bridge相容多版本的JSBridge。
至此,JSBridge的原理介紹完畢,歡迎大家轉發留言進行交流。