原因:網上其實有不少講解WebViewJavascriptBridge原理的文章,但都着重了Native端,今天從一個純前端角度出發,抓住核心脈絡講解下原理,清晰明瞭,一文即懂。前端
通訊的基礎:web
初始化方法setupWebViewJavascriptBridge,裏面的字段都是約定的,由於在native執行初始化JS的時候,會用到,好比WVJBCallbacks。由於通訊的過程是異步的(動態建立iframe,捕獲跳轉)。因此setupWebViewJavascriptBridge是異步的,而且之後調用native提供的方法也會是異步的。而且最好是在setupWebViewJavascriptBridge的回調函數中調用,能夠保證初始化成功了。而回調函數裏面會把WebViewJavascriptBridge當作參數返回。數組
上圖是網上偷懶截的圖,到時候底部放個連接。app
重點:window.WebViewJavascriptBridge,全部的交互都是經過這個WebViewJavascriptBridge對象來完成的。初始化的過程就是動態建立一個iframe,將iframe的src設置爲https://__bridge_loaded__,而後插入到頁面中。前面通訊基礎說過,natvie可以截獲h5跳轉,當Native捕獲到當前URL,而且其值等於 https://__bridge_loaded__ (當前URL是約定成俗的) ,就會注入一段自執行的代碼(假設其爲bridge,下面統一稱呼了),掛載WebViewJavascriptBridge到window上。等下會着重說下這段自執行的代碼是如何工做的。異步
if (window.WebViewJavascriptBridge), if (window.WVJBCallbacks) ,保證了初始化只執行一次。WVJBCallbacks這個字段用於尚未初始化的時候,保存回調函數。當咱們通知native端進行初始化,而且初始化以後,bridge裏面會去遍歷WVJBCallbacks中的回調函數,並將WebViewJavascriptBridge當作參數注入。執行完後會delete window.WVJBCallbacks。ide
下圖是咱們真正調用一個native的方法,假設去獲取用戶信息,WebViewJavascriptBridge是重點,下面會詳細講解下這個對象。函數
export function getUserData() { return new Promise((resolve, reject) => { setupWebViewJavascriptBridge((WebViewJavascriptBridge) => { WebViewJavascriptBridge.callHandler('xxxx', params, (data) => { resolve(data); }); }) }) }
到了這裏,其實咱們前端須要作的已經完了。我再理一理順序。getUserData,去調用native提供的方法。先調用setupWebViewJavascriptBridge,裏面有作是否初始化的判斷。而後回調函數裏面,咱們就能取到WebViewJavascriptBridge對象,對象上面掛載callHandler了方法,'xxxx'表明着和native端約定好的方法,執行便可。fetch
最上面的時候說過,native能夠執行JS,能獲取到window。因此接下來重點講一下鏈接native和js的(bridge)橋樑是如何運做的。
何時初始化bridge?
重複說一下。setupWebViewJavascriptBridge第一次調用的時候,會建立一個iframe,src指向https://__bridge_loaded__。當native截獲到這個請求的時候,判斷爲bridge_loaded,就會注入一段自執行的代碼進行WebViewJavascriptBridge初始化。
接下來看代碼:url
//若是已經初始化了,則返回。 if (window.WebViewJavascriptBridge) { return; } if (!window.onerror) { window.onerror = function(msg, url, line) { console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line); } } //初始化一些屬性。 var messagingIframe; //用於存儲消息列表 var sendMessageQueue = []; //用於存儲消息 var messageHandlers = {}; //經過下面兩個協議組合來肯定是不是特定的消息,而後攔擊。 var CUSTOM_PROTOCOL_SCHEME = 'https'; var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__'; //oc調用js的回調 var responseCallbacks = {}; //消息對應的id var uniqueId = 1; //是否設置消息超時 var dispatchMessagesWithTimeoutSafety = true; //web端註冊一個消息方法 function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler; } //web端調用一個OC註冊的消息 function callHandler(handlerName, data, responseCallback) { if (arguments.length == 2 && typeof data == 'function') { responseCallback = data; data = null; } _doSend({ handlerName: handlerName, data: data }, responseCallback); } function disableJavscriptAlertBoxSafetyTimeout() { dispatchMessagesWithTimeoutSafety = false; } //把消息轉換成JSON字符串返回 function _fetchQueue() { var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; return messageQueueString; } //OC調用JS的入口方法 function _handleMessageFromObjC(messageJSON) { _dispatchMessageFromObjC(messageJSON); } //初始化橋接對象,OC能夠經過WebViewJavascriptBridge來調用JS裏面的各類方法。 window.WebViewJavascriptBridge = { registerHandler: registerHandler, callHandler: callHandler, disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout, _fetchQueue: _fetchQueue, _handleMessageFromObjC: _handleMessageFromObjC }; //處理從OC返回的消息。 function _dispatchMessageFromObjC(messageJSON) { if (dispatchMessagesWithTimeoutSafety) { setTimeout(_doDispatchMessageFromObjC); } else { _doDispatchMessageFromObjC(); } function _doDispatchMessageFromObjC() { var message = JSON.parse(messageJSON); var messageHandler; var responseCallback; //回調 if (message.responseId) { responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else {//主動調用 if (message.callbackId) { var callbackResponseId = message.callbackId; responseCallback = function(responseData) { _doSend({ handlerName: message.handlerName, responseId: callbackResponseId, responseData: responseData }); }; } //獲取JS註冊的函數 var handler = messageHandlers[message.handlerName]; if (!handler) { console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message); } else { //調用JS中的對應函數處理 handler(message.data, responseCallback); } } } } //把消息從JS發送到OC,執行具體的發送操做。 function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); //存儲消息的回調ID responseCallbacks[callbackId] = responseCallback; //把消息對應的回調ID和消息一塊兒發送,以供消息返回之後使用。 message['callbackId'] = callbackId; } //把消息放入消息列表 sendMessageQueue.push(message); //下面這句話會出發JS對OC的調用 //讓webview執行跳轉操做,從而能夠在 //webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 中攔截到JS發給OC的消息 messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; } messagingIframe = document.createElement('iframe'); messagingIframe.style.display = 'none'; //messagingIframe.body.style.backgroundColor="#0000ff"; messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; document.documentElement.appendChild(messagingIframe); //註冊_disableJavascriptAlertBoxSafetyTimeout方法,讓OC能夠關閉回調超時,默認是開啓的。 registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout); //執行_callWVJBCallbacks方法 setTimeout(_callWVJBCallbacks, 0); //初始化WEB中註冊的方法。這個方法會把WEB中的hander註冊到bridge中。 //下面的代碼其實就是執行WEB中的callback函數。 function _callWVJBCallbacks() { var callbacks = window.WVJBCallbacks; delete window.WVJBCallbacks; for (var i = 0; i < callbacks.length; i++) { callbacks[i](WebViewJavascriptBridge); } } })();
這裏着重講 JS如何調native方法的。
window.WebViewJavascriptBridge = {spa
registerHandler: registerHandler, callHandler: callHandler, disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout, _fetchQueue: _fetchQueue, _handleMessageFromObjC: _handleMessageFromObjC };
這個對象就是上文中回調函數獲取到的WebViewJavascriptBridge 對象。callHandler表示執行某個約定的方法。
上面的源碼註釋很清晰了,源碼就不過多解讀了。接下來咱們舉一個完整的列子來闡述整個過程。
重點
getUserData => 調用setupWebViewJavascriptBridge => 由於是第一次調用進行初始化,會將回調函數保存到WVJBCallbacks中。
=> 動態建立ifram,native截獲,注入代碼進行brige初始化 => WebViewJavascriptBridge初始化 => 第一次執行因此會觸發
_callWVJBCallbacks,遍歷上面的WVJBCallbacks數組,而且將WebViewJavascriptBridge做爲參數傳入,執行回調。 => 回調中執行的就是getUserData裏面的WebViewJavascriptBridge.callHandler('xxxx')
=》 callHandler執行的其實就是__doSend方法。 =》 _doSend裏面會將getUserData裏面的回調函數保存在全局對象變量responseCallbacks中,key則是自增的ID。而且把getUserData所調用的方法名,參數,key,都放在一個對象中(message),並將這個
message,存到另一個全局數組變量sendMessageQueue中。 => 動態建立一個ifram,src爲__wvjb_queue_message__,也就是說建立的ifram,通常就兩種地址,一個是告訴native進行初始化,一個是告訴native能夠輪詢消息隊列sendMessageQueue了。 => native攔截到URL,遍歷全局變量sendMessageQueue,執行getUserData所須要的方法,這裏就是XXXX,並組裝參數 => 調用另外一個_handleMessageFromObjC方法,解析參數,獲得一開始的自增ID,從全局responseCallbacks中取到真正的回調函數執行。 => over了。完整的交互就是這個樣子的了。
上面的流程已經很清楚了,若是有不懂,或者有錯誤的地方歡迎指正。
這是js到native端的消息過程,還有native調js的,其實過程差很少。參考下文把
WebViewJavascriptBridge原理解析