柚子快報(bào)邀請碼778899分享:前端 JSBridge
在Hybrid模式下,H5會(huì)經(jīng)常需要使用Native的功能,比如打開二維碼掃描、調(diào)用原生頁面、獲取用戶信息等,同時(shí)Native也需要向Web端發(fā)送推送、更新狀態(tài)等,而JavaScript是運(yùn)行在單獨(dú)的JS Context中(Webview容器、JSCore等),與原生有運(yùn)行環(huán)境的隔離,所以需要有一種機(jī)制實(shí)現(xiàn)Native端和Web端的雙向通信,這就是JSBridge:以JavaScript引擎或Webview容器作為媒介,通過協(xié)定協(xié)議進(jìn)行通信,實(shí)現(xiàn)Native端和Web端雙向通信的一種機(jī)制。
通過JSBridge,Web端可以調(diào)用Native端的Java接口,同樣Native端也可以通過JSBridge調(diào)用Web端的JavaScript接口,實(shí)現(xiàn)彼此的雙向調(diào)用
三、WebView
首先了解下webView,webView是移動(dòng)端提供的運(yùn)行JavaScript的環(huán)境,是系統(tǒng)渲染W(wǎng)eb網(wǎng)頁的一個(gè)控件,可與頁面JavaScript交互,實(shí)現(xiàn)混合開發(fā),其中Android和iOS又有些不同:
Android的WebView采用的是低版本和高版本使用了不同的webkit內(nèi)核,4.4后直接使用了Chrome。
iOS中UIWebView算是自IOS2就有,但性能較差,特性支持較差,WKWebView是iOS8之后的升級版,性能更強(qiáng)特性支持也較好。
WebView控件除了能加載指定的url外,還可以對URL請求、JavaScript的對話框、加載進(jìn)度、頁面交互進(jìn)行強(qiáng)大的處理,之后會(huì)提到攔截請求、執(zhí)行JS腳本都依賴于此。
四、JSB實(shí)現(xiàn)原理
Web端和Native可以類比于Client/Server模式,Web端調(diào)用原生接口時(shí)就如同Client向Server端發(fā)送一個(gè)請求類似,JSB在此充當(dāng)類似于HTTP協(xié)議的角色,實(shí)現(xiàn)JSBridge主要是兩點(diǎn):
將Native端原生接口封裝成JavaScript接口將Web端JavaScript接口封裝成原生接口
4.1 Native->Web
首先來說Native端調(diào)用Web端,這個(gè)比較簡單,JavaScript作為解釋性語言,最大的一個(gè)特性就是可以隨時(shí)隨地地通過解釋器執(zhí)行一段JS代碼,所以可以將拼接的JavaScript代碼字符串,傳入JS解析器執(zhí)行就可以,JS解析器在這里就是webView。
Android 4.4之前只能用loadUrl來實(shí)現(xiàn),并且無法執(zhí)行回調(diào):
String jsCode = String.format("window.showWebDialog('%s')", text);
webView.loadUrl("javascript: " + jsCode);
Android 4.4之后提供了evaluateJavascript來執(zhí)行JS代碼,并且可以獲取返回值執(zhí)行回調(diào):
String jsCode = String.format("window.showWebDialog('%s')", text);
webView.evaluateJavascript(jsCode, new ValueCallback
@Override
public void onReceiveValue(String value) {
}
});
iOS的UIWebView使用stringByEvaluatingJavaScriptFromString:
NSString *jsStr = @"執(zhí)行的JS代碼";
[webView stringByEvaluatingJavaScriptFromString:jsStr];
iOS的WKWebView使用evaluateJavaScript:
[webView evaluateJavaScript:@"執(zhí)行的JS代碼" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
}];
4.2 Web->Native
Web調(diào)用Native端主要有兩種方式
4.2.1 攔截Webview請求的URL Schema
URL Schema是類URL的一種請求格式,格式如下:
我們可以自定義JSBridge通信的URL Schema,比如:jsbridge://showToast?text=hello
Native加載WebView之后,Web發(fā)送的所有請求都會(huì)經(jīng)過WebView組件,所以Native可以重寫WebView里的方法,從來攔截Web發(fā)起的請求,我們對請求的格式進(jìn)行判斷:
如果符合我們自定義的URL Schema,對URL進(jìn)行解析,拿到相關(guān)操作、操作,進(jìn)而調(diào)用原生Native的方法如果不符合我們自定義的URL Schema,我們直接轉(zhuǎn)發(fā),請求真正的服務(wù)
Web發(fā)送URL請求的方法有這么幾種:
a標(biāo)簽location.href使用iframe.src發(fā)送ajax請求
這些方法,a標(biāo)簽需要用戶操作,location.href可能會(huì)引起頁面的跳轉(zhuǎn)丟失調(diào)用,發(fā)送ajax請求Android沒有相應(yīng)的攔截方法,所以使用iframe.src是經(jīng)常會(huì)使用的方案:
安卓提供了shouldOverrideUrlLoading方法攔截UIWebView使用shouldStartLoadWithRequest,WKWebView則使用decidePolicyForNavigationAction
這種方式從早期就存在,兼容性很好,但是由于是基于URL的方式,長度受到限制而且不太直觀,數(shù)據(jù)格式有限制,而且建立請求有時(shí)間耗時(shí)。
4.2.2 向Webview中注入JS API
這個(gè)方法會(huì)通過webView提供的接口,App將Native的相關(guān)接口注入到JS的Context(window)的對象中,一般來說這個(gè)對象內(nèi)的方法名與Native相關(guān)方法名是相同的,Web端就可以直接在全局window下使用這個(gè)暴露的全局JS對象,進(jìn)而調(diào)用原生端的方法。
這個(gè)過程會(huì)更加簡單直觀,不過有兼容性問題,大多數(shù)情況下都會(huì)使用這種方式
Android(4.2+)提供了addJavascriptInterface注入:
// 注入全局JS對象
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
class NativeBridge {
private Context ctx;
NativeBridge(Context ctx) {
this.ctx = ctx;
}
// 增加JS調(diào)用接口
@JavascriptInterface
public void showNativeDialog(String text) {
new AlertDialog.Builder(ctx).setMessage(text).create().show();
}
}
在Web端直接調(diào)用這個(gè)方法即可:
window.NativeBridge.showNativeDialog('hello');
iOS的UIWebView提供了JavaSciptCore
iOS的WKWebView提供了WKScriptMessageHandler
4.3 帶回調(diào)的調(diào)用
上面已經(jīng)說到了Native、Web間雙向通信的兩種方法,但站在一端而言還是一個(gè)單向通信的過程 ,比如站在Web的角度:Web調(diào)用Native的方法,Native直接相關(guān)操作但無法將結(jié)果返回給Web,但實(shí)際使用中會(huì)經(jīng)常需要將操作的結(jié)果返回,也就是JS回調(diào)。
所以在對端操作并返回結(jié)果,有輸入有輸出才是完整的調(diào)用,那如何實(shí)現(xiàn)呢?
其實(shí)基于之前的單向通信就可以實(shí)現(xiàn),我們在一端調(diào)用的時(shí)候在參數(shù)中加一個(gè)callbackId標(biāo)記對應(yīng)的回調(diào),對端接收到調(diào)用請求后,進(jìn)行實(shí)際操作,如果帶有callbackId,對端再進(jìn)行一次調(diào)用,將結(jié)果、callbackId回傳回來,這端根據(jù)callbackId匹配相應(yīng)的回調(diào),將結(jié)果傳入執(zhí)行就可以了。
可以看到實(shí)際上還是通過兩次單項(xiàng)通信實(shí)現(xiàn)的。
以Android,在Web端實(shí)現(xiàn)帶有回調(diào)的JSB調(diào)用為例:
// Web端代碼:
// Android端代碼
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
class NativeBridge {
private Context ctx;
NativeBridge(Context ctx) {
this.ctx = ctx;
}
// 獲取Native端輸入值
@JavascriptInterface
public void getNativeEditTextValue(int callbackId) {
MainActivity mainActivity = (MainActivity)ctx;
// 獲取Native端輸入框的value
String value = mainActivity.editText.getText().toString();
// 需要注入在Web執(zhí)行的JS代碼
String jsCode = String.format("window.JSSDK.receiveMessage(%s, '%s')", callbackId, value);
// 在UI線程中執(zhí)行
mainActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mainActivity.webView.evaluateJavascript(jsCode, null);
}
});
}
}
以上代碼簡單實(shí)現(xiàn)了一個(gè)demo,在Web端點(diǎn)擊按鈕,會(huì)獲取Native端輸入框的值,并將值以Web端彈窗展現(xiàn),這樣就實(shí)現(xiàn)了Web->Native帶有回調(diào)的JSB調(diào)用,同理Native->Web也是同樣的邏輯,不同的只是將callback保存在Native端罷了,在此就不詳細(xì)論述了。
五、開源的JSBridge
可以看到,實(shí)現(xiàn)一個(gè)完整的JSBridge還是挺麻煩的,還需要考慮低端機(jī)型的兼容問題、同步異步調(diào)用問題,好在已經(jīng)有開源的JSBridge供我們直接使用了:
DSBridge,主要通過注入API的形式,DSBridge for Android、DSBridge for IOSJsBridge,主要通過攔截URL Schema,JsBridge
以DSBridge-Android為例:
// Web端代碼
// 引入SDK
// Android代碼
// 使用dwebView替換原生webView
dwebView.addJavascriptObject(new JsApi(), null);
class JSApi {
private Context ctx;
public JSApi (Context ctx) {
this.ctx = ctx;
}
@JavascriptInterface
public void getNativeEditTextValue(Object msg, CompletionHandler
String value = ((MainActivity)ctx).editText.getText().toString();
// 通過handler將value傳給Web端,實(shí)現(xiàn)回調(diào)的JSB調(diào)用
handler.completed(value);
}
}
柚子快報(bào)邀請碼778899分享:前端 JSBridge
相關(guān)文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。