Javascript之JSBridge初探
JSBridge 的起源
近些年,移動(dòng)端普及化越來(lái)越高,開(kāi)發(fā)過(guò)程中選用 Native 還是 H5 一直是熱門(mén)話(huà)題。Native 和 H5 都有著各自的優(yōu)缺點(diǎn),為了滿(mǎn)足業(yè)務(wù)的需要,公司實(shí)際項(xiàng)目的開(kāi)發(fā)過(guò)程中往往會(huì)融合兩者進(jìn)行 Hybrid 開(kāi)發(fā)。Native 和 H5 分處兩地,看起來(lái)無(wú)法聯(lián)系,那么如何才能讓雙方協(xié)同實(shí)現(xiàn)功能呢?
這時(shí)我們想到了 Codova ,Codova 提供了一組與設(shè)備相關(guān)的 API ,是早期js調(diào)用原生代碼來(lái)實(shí)現(xiàn)原生功能的常用方案。不過(guò) JSBridge 真正在國(guó)內(nèi)廣泛應(yīng)用是由于移動(dòng)互聯(lián)網(wǎng)的盛行。
JSBridge 是一種 JS 實(shí)現(xiàn)的 Bridge,連接著橋兩端的 Native 和 H5。它在 APP 內(nèi)方便地讓 Native 調(diào)用 JS,JS 調(diào)用 Native ,是雙向通信的通道。JSBridge 主要提供了 JS 調(diào)用 Native代碼的能力,實(shí)現(xiàn)原生功能如查看本地相冊(cè)、打開(kāi)攝像頭、指紋支付等。
H5 與 Native 對(duì)比
name | H5 | Native |
---|---|---|
穩(wěn)定性 | 調(diào)用系統(tǒng)瀏覽器內(nèi)核,穩(wěn)定性較差 | 使用原生內(nèi)核,更加穩(wěn)定 |
靈活性 | 版本迭代快,上線(xiàn)靈活 | 迭代慢,需要應(yīng)用商店審核,上線(xiàn)速度受限制 |
受網(wǎng)速 影響 | 較大 | 較小 |
流暢度 | 有時(shí)加載慢,給用戶(hù)“卡頓”的感覺(jué) | 加載速度快,更加流暢 |
用戶(hù)體驗(yàn) | 功能受瀏覽器限制,體驗(yàn)有時(shí)較差 | 原生系統(tǒng) api 豐富,能實(shí)現(xiàn)的功能較多,體驗(yàn)較好 |
可移植性 | 兼容跨平臺(tái)跨系統(tǒng),如 PC 與 移動(dòng)端,iOS 與 Android | 可移植性較低,對(duì)于 iOS 和 Android 需要維護(hù)兩套代碼 |
JSBridge 的雙向通信原理
JS 調(diào)用 Native
JS 調(diào)用 Native 的實(shí)現(xiàn)方式較多,主要有攔截URL Scheme、重寫(xiě) prompt 、注入 API 等方法。
攔截 URL Scheme
Android 和 iOS 都可以通過(guò)攔截 URL Scheme 并解析 Scheme 來(lái)決定是否進(jìn)行對(duì)應(yīng)的 Native 代碼邏輯處理。
Android 的話(huà),Webview提供了shouldOverrideUrlLoading方法來(lái)提供給 Native 攔截 H5 發(fā)送的URL Scheme請(qǐng)求。代碼如下:
public class CustomWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { ...... // 場(chǎng)景一: 攔截請(qǐng)求、接收 scheme if (url.equals("xxx")) { // handle ... // callback view.loadUrl("JavaScript:setAllContent(" + json + ");") return true; } return super.shouldOverrideUrlLoading(url); } }
iOS 的WKWebview可以根據(jù)攔截到的URL Scheme和對(duì)應(yīng)的參數(shù)執(zhí)行相關(guān)的操作。代碼如下:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ if ([navigationAction.request.URL.absoluteString hasPrefix:@"xxx"]) { [[UIApplication sharedApplication] openURL:navigationAction.request.URL]; } decisionHandler(WKNavigationActionPolicyAllow); }
這種方法的優(yōu)點(diǎn)是不存在漏洞問(wèn)題、使用靈活,可以實(shí)現(xiàn) H5 和 Native 頁(yè)面的無(wú)縫切換。例如在某一頁(yè)面需要快速上線(xiàn)的情況下,先開(kāi)發(fā)出 H5 頁(yè)面。某一鏈接填寫(xiě)的是 H5 鏈接,在對(duì)應(yīng)的 Native 頁(yè)面開(kāi)發(fā)完成前先跳轉(zhuǎn)至 H5 頁(yè)面,待 Native 頁(yè)面開(kāi)發(fā)完后再進(jìn)行攔截,跳轉(zhuǎn)至 Native 頁(yè)面,此時(shí) H5 的鏈接無(wú)需進(jìn)行修改。但是使用 iframe.src 來(lái)發(fā)送URL Scheme需要對(duì) URL 的長(zhǎng)度作控制,使用復(fù)雜,速度較慢。
重寫(xiě) prompt 等原生 JS 方法
Android 4.2 之前注入對(duì)象的接口是 addJavaScriptInterface ,但是由于安全原因慢慢不被使用。一般會(huì)通過(guò)修改瀏覽器的部分 Window 對(duì)象的方法來(lái)完成操作。主要是攔截 alert、confirm、prompt、console.log 四個(gè)方法,分別被Webview的 onJsAlert、onJsConfirm、onConsoleMessage、onJsPrompt 監(jiān)聽(tīng)。其中 onJsPrompt 監(jiān)聽(tīng)的代碼如下:
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) { String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message,defaultValue); xxx; return true; }
iOS 由于安全機(jī)制,WKWebView對(duì) alert、confirm、prompt 等方法做了攔截,如果通過(guò)此方式進(jìn)行 Native 與 JS 交互,需要實(shí)現(xiàn)WKWebView的三個(gè)WKUIDelegate代理方法。代碼示例如下:
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:message?:@"" preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:([UIAlertAction actionWithTitle:@"確認(rèn)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { completionHandler(); }])]; [self presentViewController:alertController animated:YES completion:nil]; }
使用該方式時(shí),可以與 Android 和 iOS 約定好使用傳參的格式,這樣 H5 可以無(wú)需識(shí)別客戶(hù)端,傳入不同參數(shù)直接調(diào)用 Native 即可。剩下的交給客戶(hù)端自己去攔截相同的方法,識(shí)別相同的參數(shù),進(jìn)行自己的處理邏輯即可實(shí)現(xiàn)多端表現(xiàn)一致。如:
alert("確定xxx?", "取消", "確定", callback());
另外,如果能與 Native 確定好方法名、傳參等調(diào)用的協(xié)議規(guī)范,這樣其它格式的 prompt 等方法是不會(huì)被識(shí)別的,能起到隔離的作用。
##### 注入 API
基于Webview提供的能力,我們可以向 Window 上注入對(duì)象或方法。JS 通過(guò)這個(gè)對(duì)象或方法進(jìn)行調(diào)用時(shí),執(zhí)行對(duì)應(yīng)的邏輯操作,可以直接調(diào)用 Native 的方法。使用該方式時(shí),JS 需要等到 Native 執(zhí)行完對(duì)應(yīng)的邏輯后才能進(jìn)行回調(diào)里面的操作。
Android 的Webview提供了 addJavascriptInterface 方法,支持 Android 4.2 及以上系統(tǒng)。
gpcWebView.addJavascriptInterface(new JavaScriptInterface(), 'nativeApiBridge'); public class JavaScriptInterface { Context mContext; JavaScriptInterface(Context c) { mContext = c; } public void share(String webMessage){ // Native 邏輯 } }
JS 調(diào)用示例:
window.NativeApi.share(xxx);
iOS 的UIWebview提供了 JavaScriptScore 方法,支持 iOS 7.0 及以上系統(tǒng)。WKWebview提供了 window.webkit.messageHandlers 方法,支持 iOS 8.0 及以上系統(tǒng)。UIWebview在幾年前常用,目前已不常見(jiàn)。以下為創(chuàng)建WKWebViewConfiguration和 創(chuàng)建 WKWebView 示例:
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; WKPreferences *preferences = [WKPreferences new]; preferences.javaScriptCanOpenWindowsAutomatically = YES; preferences.minimumFontSize = 40.0; configuration.preferences = preferences; - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"share"]; [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"pickImage"]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"share"]; [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"pickImage"]; }
JS 調(diào)用示例:
window.webkit.messageHandlers.share.postMessage(xxx);
Native 調(diào)用 JS
Native 調(diào)用 JS 比較簡(jiǎn)單,只要 H5 將 JS 方法暴露在 Window 上給 Native 調(diào)用即可。
Android 中主要有兩種方式實(shí)現(xiàn)。在 4.4 以前,通過(guò) loadUrl 方法,執(zhí)行一段 JS 代碼來(lái)實(shí)現(xiàn)。在 4.4 以后,可以使用 evaluateJavascript 方法實(shí)現(xiàn)。loadUrl 方法使用起來(lái)方便簡(jiǎn)潔,但是效率低無(wú)法獲得返回結(jié)果且調(diào)用的時(shí)候會(huì)刷新 WebView。evaluateJavascript 方法效率高獲取返回值方便,調(diào)用時(shí)候不刷新WebView,但是只支持 Android 4.4+。相關(guān)代碼如下:
webView.loadUrl("javascript:" + javaScriptString); webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() { @Override public void onReceiveValue(String value){ xxx } });
iOS 在WKWebview中可以通過(guò) evaluateJavaScript:javaScriptString 來(lái)實(shí)現(xiàn),支持 iOS 8.0 及以上系統(tǒng)。
// swift func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil) // javaScriptString 需要調(diào)用的 JS 代碼 // completionHandler 執(zhí)行后的回調(diào) // objective-c [jsContext evaluateJavaScript:@"ZcyJsBridge(ev, data)"]
JSBridge 的使用
如何引用
- 由 H5 引用 在我司移動(dòng)端初期版本時(shí)采用的是該方式,采用本地引入npm包的方式進(jìn)行調(diào)用。這種方式可以確定 JSBridge 是存在的,可直接調(diào)用 Native 方法。但是如果后期 Bridge 的實(shí)現(xiàn)方式改變,雙方需要做更多的兼容,維護(hù)成本高
- 由 Native 注入 這是當(dāng)前我司移動(dòng)端選用的方式。在考慮到后期業(yè)務(wù)需要的情況下,進(jìn)行了重新設(shè)計(jì),選用 Native 注入的方式來(lái)引用 JSBridge。這樣有利于保持 API 與 Native 的一致性,但是缺點(diǎn)是在 Native 注入的方法和時(shí)機(jī)都受限,JS 調(diào)用 Native 之前需要先判斷 JSBridge 是否注入成功
使用規(guī)范
H5 調(diào)用 Native 方法的偽代碼實(shí)例,如:
params = { api_version: "xxx", // API 版本 title: "xxx", // 標(biāo)題 filename: "xxx", // 文件名稱(chēng) image: "xxx", // 圖片鏈接 url: "xxx", // 網(wǎng)址鏈接 success: function (res) { xxx; // 調(diào)用成功后執(zhí)行 }, fail: function (err) { if (err.code == '-2') { fail && fail(err); // 調(diào)用了當(dāng)前客戶(hù)端中不存在的 API 版本 } else { const msg = err.msg; //異常信息 Toast.fail(msg); } } }; window.NativeApi.share(params);
以下簡(jiǎn)要列出通用方法的抽象,目前基本遵循以下規(guī)范進(jìn)行雙端通信。
window.NativeApi.xxx({ api_version:'', name: "xxx", path: "xxx", id: "xxx", success: function (res) { console.log(res); }, fail: function (err) { console.log(err); } });
由于初期版本選擇了由 H5 本地引用 JSBridge,后期采用 Native 注入的方式?,F(xiàn)有的 H5 需要對(duì)各種情況做兼容,邏輯抽象如下:
reqNativeBridge(vm, fn) { if (!isApp()) { // 如果不在 APP 內(nèi)進(jìn)行調(diào)用 vm.$dialog.alert({ message: "此功能需要訪(fǎng)問(wèn) APP 才能使用", }); } else { if (!window.NativeApi) { // 針對(duì)初期版本 vm.$dialog.alert({ message: "請(qǐng)更新到最新 APP 使用該功能", }); } else { // 此處只針對(duì)“調(diào)用了當(dāng)前客戶(hù)端中不存在的 API 版本”的報(bào)錯(cuò)進(jìn)行處理 // 其余種類(lèi)的錯(cuò)誤信息交由具體的業(yè)務(wù)去處理 fn && fn((err) => { vm.$dialog.alert({ message: "請(qǐng)更新到最新 APP 使用該功能", }); }); } } }
總結(jié)
上述內(nèi)容簡(jiǎn)要介紹了 JSBridge 的部分原理,希望對(duì)從未了解過(guò) JSBridge 的同學(xué)能有所幫助。如果需要更深入的了解 JSBridge 的原理和實(shí)現(xiàn),如 JSBridge 接口調(diào)用的封裝實(shí)現(xiàn),JS 調(diào)用 Native 時(shí)的回調(diào)的唯一性等。大家可以去查閱更多資料,參考更詳細(xì)的相關(guān)文檔或他人的整理成文的沉淀。
以上就是Javascript之JSBridge初探的詳細(xì)內(nèi)容,更多關(guān)于Javascript之JSBridge的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Flutter使用JsBridge方式處理Webview與H5通信的方法
- android和js的交互之jsbridge使用教程
- Android中極簡(jiǎn)的js與java的交互庫(kù)(SimpleJavaJsBridge)
- 微信WeixinJSBridge API使用實(shí)例
- 微信內(nèi)置瀏覽器私有接口WeixinJSBridge介紹
- 微信瀏覽器內(nèi)置JavaScript對(duì)象WeixinJSBridge使用實(shí)例
- 微信內(nèi)置瀏覽器WeixinJSBridge的使用技巧(隱藏右上角按鈕,獲取用戶(hù)網(wǎng)絡(luò)狀態(tài),支付等)
相關(guān)文章
JavaScript中實(shí)現(xiàn)sprintf、printf函數(shù)
這篇文章主要介紹了JavaScript中實(shí)現(xiàn)sprintf、printf函數(shù),這兩個(gè)函數(shù)在大多數(shù)編程語(yǔ)言中都有,但JS中卻沒(méi)有,本文介紹在js中實(shí)現(xiàn)這兩個(gè)函數(shù)功能,需要的朋友可以參考下2015-01-01js點(diǎn)擊列表文字對(duì)應(yīng)該行顯示背景顏色的實(shí)現(xiàn)代碼
這篇文章主要介紹了js點(diǎn)擊列表文字對(duì)應(yīng)該行顯示背景顏色的實(shí)現(xiàn)代碼,感興趣的小伙伴可以參考下2015-08-08Kibo 用于處理鍵盤(pán)事件的Javascript工具庫(kù)
Kibo是一個(gè)簡(jiǎn)單的用于處理鍵盤(pán)事件的Javascript工具庫(kù)。2011-10-10canvas+gif.js打造自己的數(shù)字雨頭像的示例代碼
本篇文章主要介紹了canvas+gif.js打造自己的數(shù)字雨頭像的示例代碼,這里整理了詳細(xì)的代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10在layui下對(duì)元素進(jìn)行事件綁定的實(shí)例
今天小編就為大家分享一篇在layui下對(duì)元素進(jìn)行事件綁定的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09js如何去除數(shù)組中的empty?undefined空項(xiàng)
這篇文章主要介紹了js如何去除數(shù)組中的empty?undefined空項(xiàng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08