特別說明
首先說明本文并非原創,原文在此:https://www.cnblogs.com/dailc/p/5931324.html#callback-format。相關代碼會放在https://github.com/Samuel2306/JSBridge。
什么是JSBridge
小伙伴如果如果對于Hybrid并沒有什么概念,請先閱讀筆者的另外一篇文章《Hybrid開發——Android與H5的親密接觸》。JSBridge就是一個簡化Native與JS通信的框架。JSBridge定義了統一的通信過程:Native只通過一個固定的橋對象調用JS,JS也只通過固定的橋對象調用Native,具體流程如下圖所示:
說明:JS觸發原生的回調函數的過程其實也是觸發一個url schema
url scheme介紹
? url scheme是一種類似于url的鏈接,是為了方便app直接互相調用設計的
可以用系統的OpenURI打開一個類似于url的鏈接(可拼入參數),然后系統會進行判斷,如果是系統的url scheme,則打開系統應用,否則找看是否有app注冊這種scheme,打開對應app需要注意的是,這種scheme必須原生app注冊后才會生效,如微信的scheme為(weixin://)
??本文JSBridge中的url scheme則是仿照上述的形式的一種方式
app不會注冊對應的scheme,而是由前端頁面通過某種方式觸發scheme(如用iframe.src),然后Native用某種方法捕獲對應的url觸發事件,然后拿到當前的觸發url,根據定義好的協議,分析當前觸發了那種方法。簡而言之就是url => 方法(對應關系是JS開發與Android開發商量定義好的)。
為什么要用JSBridge
在筆者的《Hybrid開發——Android與H5的親密接觸》一文中,我們知道Android已經可以跟JS進行交互,那為什么還要這種通過url scheme的JSBridge方式呢,原因大致如下:
? Android4.2以下,addJavascriptInterface方式有安全漏掉
??iOS7以下, JS無法調用Native
url scheme交互方式是一套現有的成熟方案,可以完美兼容各種版本,不存在上述問題。而且JSBridge將Native與JS的交互方式進行解耦,提供了一個可擴展的,高可用的,穩定的解決方案。
實現一個JSBridge
實現步驟大致如下:
第一步: 設計出一個Native與JS交互的全局橋對象
第二步: JS調用Native功能實現
第三步: Native監聽api調用
第四步: Native分析url-參數和回調的格式
第五步: Native調用JS功能實現
第六步: H5中api方法的注冊
第一步: 設計出一個Native與JS交互的全局橋對象
我們規定,JS和Native之間的通信必須通過一個H5全局對象JSBridge來實現,該全局對象有如下特點:
? 該對象名為“JSBridge”,是H5頁面中全局對象window的一個屬性
window.JSBridge = JSBridge
? 該對象有如下方法
1、registerHandler(?String handlerName, Function handler):供H5調用。用來注冊JS方法,Native可通過JSBridge調用注冊的JS方法。調用registerHandler方法后,被注冊的方法 handler 會被保存到本地變量 messageHandlers?中。
2、callHandler(?String handlerName, JSON data, Function callback):供H5調用。H5調用原生開放的api,調用后實際上還是本地通過url scheme觸發。調用時會將回調 callback 的 id(由callHandler函數生成)存放到本地變量responseCallbacks中。
3、_handleMessageFromNative(?JSON?):供Native調用。原生調用H5頁面注冊的方法,或者通知H5頁面執行回調方法(H5調用原生方法時,會將回調函數作為參數進行傳遞,就是為了讓原生能在適當的時候通過JSBridge調用回調函數,也就是responseCallbacks里面對應的函數)。
第二步: JS調用Native功能實現
上一步我們已經定義好了JSBridge對象,H5可以通過callHandler方法來調用原生的API,那么callHandler內部經歷了一個怎么樣的過程呢?接下來我們就來說說callHandler(?String?handlerName, JSON data, Function callback)函數內部實現過程。
在執行callHandler(?String?handlerName, JSON data, Function callback)時,內部經歷了以下步驟:
(1)首先判斷H5調用該方法的時候有沒有傳入回調函數。如果有回調函數,則生成一個回調函數id, 并將id和對應的回調函數添加進入回調函數的集合?responseCallbacks 中。
(2)通過特定的參數轉換方法,將傳入的數據(data), 方法名(handlerName)一起,拼接成一個url scheme
// url scheme的格式如下,基本有用信息就是后面的callbackId,handlerName與data?
// 原生捕獲到這個scheme后會進行分析
var uri = CUSTOM_PROTOCOL_SCHEME://API_Name:callbackId/handlerName?data
(3)使用內部早就創建好的一個隱藏iframe來觸發scheme
//創建隱藏iframe過程
var messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
document.documentElement.appendChild(messagingIframe);
//觸發scheme
messagingIframe.src = uri;
注意1:正常來說是可以通過window.location.href達到發起網絡請求的效果的,但是有一個很嚴重的問題,就是如果我們連續多次修改window.location.href的值,在Native層只能接收到最后一次請求,前面的請求都會被忽略掉。所以JS端發起網絡請求的時候,需要使用iframe,這樣就可以避免這個問題。
注意2:H5調用Native時,Native處理完畢后一定要及時通知H5進行回調,要不然這個回調函數不會自動銷毀,多了后會引發內存泄漏
第三步: Native監聽api調用
在上一步中,我們已經成功在H5頁面中觸發scheme,那么Native如何捕獲scheme被觸發呢?接下來我們以Android為例,說說Android如何捕獲url scheme,不過在此之前我們先來簡單了解幾個東西:WebView、WebViewClient、WebChromeClient。
在Android的Webview設計中,不是所有功能都由WebView類來實現的,部分功能由其他的類(WebViewClient、WebChromeClient)來實現,這樣一來WebView主要專心干好解析、渲染工作就行了。
WebViewClient幫助WebView處理各種通知、請求事件的,具體來說包括:onLoadResource、onPageStart、onPageFinish、onReceiveError、onReceivedHttpAuthRequest;
WebChromeClient輔助WebView處理Javascript的對話框,網站圖標,網站title,加載進度。具體來說包括onCloseWindow(關閉WebView)、onCreateWindow()、onJsAlert (WebView上alert是彈不出來東西的,需要定制你的WebChromeClient處理彈出)、onJsPrompt、onJsConfirm、onProgressChanged、onReceivedIcon、onReceivedTitle;
在Android中通過 WebViewClient 的 shouldoverrideurlloading 可以捕獲到url scheme的觸發:
// WebViewClient主要幫助WebView處理各種通知、請求事件
private WebViewClient webViewClient = new WebViewClient() {
????@Override // 重寫shouldOverrideUrlLoading()方法,使得打開網頁時不調用系統瀏覽器, 而是在本WebView中顯示 public ????boolean shouldOverrideUrlLoading(WebView view, String url) {
? ? ? ? // 原生通過解析url進行相應處理
????}
};
另外,Android中也可以不通過iframe.src來觸發scheme,android中可以通過window.prompt(uri, "");來觸發scheme,然后Native中通過重寫WebViewClient的onJsPrompt來獲取uri
第四步:分析url-參數和回調的格式
Native接收到Url后,可以按照這種格式將回調參數id、api名、參數提取出來, 然后按如下步驟進行:
(1) 根據api名,在本地找尋對應的api方法, 并且記錄該方法執行完后的回調函數id
(2) 根據提取出來的參數,根據定義好的參數進行轉化
(3) 原生本地執行對應的api功能方法
(4) 功能執行完畢后,找到這次api調用對應的回調函數id,然后連同需要傳遞的參數信息,組裝成一個JSON格式的參數
JSON格式為: {
????responseId: 回調id,??
? ??responseData: 回調數據
}
?? responseId(String型)H5頁面中對應需要執行的回調函數的id,在H5中生成url scheme時就已經產生
?? responseData(JSON型) Native需要傳遞給H5的回調數據,是一個JSON格式:?{
????????code:(整型,調用是否成功,1成功,0失敗),
????????result:具體需要傳遞的結果信息,可以為任意類型,
????????msg:一些其它
}
(5)? 通過JSBridge通知H5頁面回調
第五步: Native調用JS功能實現
到了這一步,就該Native通過JSBridge調用H5的JS方法或者通知H5進行回調了,具體如下:
JSBridge._handleMessageFromNative(messageJSON);
messageJSON數據格式根據兩種不同的類型,有所區別:
Native通知H5頁面進行回調,messageJSON數據格式如下:
?{
????responseId: 回調id,??
? ??responseData: 回調數據
}
Native主動調用H5方法,messageJSON數據格式如下:
{
? ??handlerName:? 需要調用的h5 api的名稱
? ??data:? 需要傳遞的數據,固定為JSON格式
? ??callbackId:? 原生生成的回調函數id,h5執行完畢后通過url scheme通知原生api成功執行并傳遞參數
}
第六步: H5中api方法的注冊
//注冊一個測試函數
JSBridge.registerHandler('testH5Func',? function(data,? callback){
????alert('測試函數接收到數據:'+JSON.stringify(data));
????callback&&callback('測試回傳數據...');
});
data: 原生傳過來的數據;
callback: 內部封裝過一次的, 執行callback后會觸發url scheme, 通知原生獲取回調信息。
結語
自此,我們已經將JSBridge的整體架構和實現思路都講明白了,本篇文章是關于JS部分的實現。