摘要:并且最好是在的回調(diào)函數(shù)中調(diào)用,可以保證初始化成功了。當(dāng)我們通知端進(jìn)行初始化,并且初始化之后,里面會(huì)去遍歷中的回調(diào)函數(shù),并將當(dāng)做參數(shù)注入。里面會(huì)將里面的回調(diào)函數(shù)保存在全局對(duì)象變量中則是自增的。
緣由:網(wǎng)上其實(shí)有很多講解WebViewJavascriptBridge原理的文章,但都著重了Native端,今天從一個(gè)純前端角度出發(fā),抓住核心脈絡(luò)講解下原理,清晰明了,一文即懂。
通信的基礎(chǔ):
native端到j(luò)s端。native能獲取到window環(huán)境,執(zhí)行JS。
js端到native端。native能截獲H5頁(yè)面跳轉(zhuǎn),故而JS端可以通過(guò)動(dòng)態(tài)創(chuàng)建iframe來(lái)告訴native,我發(fā)請(qǐng)求了。(其實(shí)就是在window下維護(hù)了一個(gè)messageQueue數(shù)組,然后js創(chuàng)建個(gè)iframe,告訴native,我向你發(fā)請(qǐng)求了,但具體是什么請(qǐng)求,url里面是不會(huì)體現(xiàn)的,需要native去遍歷messageQueue數(shù)組,畢竟native能取到window環(huán)境 )。
首先初始化JS端環(huán)境。初始化方法setupWebViewJavascriptBridge,里面的字段都是約定的,因?yàn)樵趎ative執(zhí)行初始化JS的時(shí)候,會(huì)用到,比如WVJBCallbacks。因?yàn)橥ㄐ诺倪^(guò)程是異步的(動(dòng)態(tài)創(chuàng)建iframe,捕獲跳轉(zhuǎn))。所以setupWebViewJavascriptBridge是異步的,并且以后調(diào)用native提供的方法也會(huì)是異步的。并且最好是在setupWebViewJavascriptBridge的回調(diào)函數(shù)中調(diào)用,可以保證初始化成功了。而回調(diào)函數(shù)里面會(huì)把WebViewJavascriptBridge當(dāng)做參數(shù)返回。
上圖是網(wǎng)上偷懶截的圖,到時(shí)候底部放個(gè)鏈接。
重點(diǎn):window.WebViewJavascriptBridge,所有的交互都是通過(guò)這個(gè)WebViewJavascriptBridge對(duì)象來(lái)完成的。初始化的過(guò)程就是動(dòng)態(tài)創(chuàng)建一個(gè)iframe,將iframe的src設(shè)置為https://__bridge_loaded__,然后插入到頁(yè)面中。前面通信基礎(chǔ)說(shuō)過(guò),natvie能夠截獲h5跳轉(zhuǎn),當(dāng)Native捕獲到當(dāng)前URL,并且其值等于 https://__bridge_loaded__ (當(dāng)前URL是約定成俗的) ,就會(huì)注入一段自執(zhí)行的代碼(假設(shè)其為bridge,下面統(tǒng)一稱呼了),掛載WebViewJavascriptBridge到window上。等下會(huì)著重說(shuō)下這段自執(zhí)行的代碼是如何工作的。
if (window.WebViewJavascriptBridge), if (window.WVJBCallbacks) ,保證了初始化只執(zhí)行一次。WVJBCallbacks這個(gè)字段用于還沒(méi)有初始化的時(shí)候,保存回調(diào)函數(shù)。當(dāng)我們通知native端進(jìn)行初始化,并且初始化之后,bridge里面會(huì)去遍歷WVJBCallbacks中的回調(diào)函數(shù),并將WebViewJavascriptBridge當(dāng)做參數(shù)注入。執(zhí)行完后會(huì)delete window.WVJBCallbacks。
下圖是我們真正調(diào)用一個(gè)native的方法,假設(shè)去獲取用戶信息,WebViewJavascriptBridge是重點(diǎn),下面會(huì)詳細(xì)講解下這個(gè)對(duì)象。
export function getUserData() { return new Promise((resolve, reject) => { setupWebViewJavascriptBridge((WebViewJavascriptBridge) => { WebViewJavascriptBridge.callHandler("xxxx", params, (data) => { resolve(data); }); }) }) }
到了這里,其實(shí)我們前端需要做的已經(jīng)完了。我再理一理順序。getUserData,去調(diào)用native提供的方法。先調(diào)用setupWebViewJavascriptBridge,里面有做是否初始化的判斷。然后回調(diào)函數(shù)里面,我們就能取到WebViewJavascriptBridge對(duì)象,對(duì)象上面掛載callHandler了方法,"xxxx"代表著和native端約定好的方法,執(zhí)行即可。
WebViewJavascriptBridge最上面的時(shí)候說(shuō)過(guò),native可以執(zhí)行JS,能獲取到window。所以接下來(lái)重點(diǎn)講一下連接native和js的(bridge)橋梁是如何運(yùn)作的。
什么時(shí)候初始化bridge?
重復(fù)說(shuō)一下。setupWebViewJavascriptBridge第一次調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)iframe,src指向https://__bridge_loaded__。當(dāng)native截獲到這個(gè)請(qǐng)求的時(shí)候,判斷為bridge_loaded,就會(huì)注入一段自執(zhí)行的代碼進(jìn)行WebViewJavascriptBridge初始化。
接下來(lái)看代碼:
//如果已經(jīng)初始化了,則返回。 if (window.WebViewJavascriptBridge) { return; } if (!window.onerror) { window.onerror = function(msg, url, line) { console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line); } } //初始化一些屬性。 var messagingIframe; //用于存儲(chǔ)消息列表 var sendMessageQueue = []; //用于存儲(chǔ)消息 var messageHandlers = {}; //通過(guò)下面兩個(gè)協(xié)議組合來(lái)確定是否是特定的消息,然后攔擊。 var CUSTOM_PROTOCOL_SCHEME = "https"; var QUEUE_HAS_MESSAGE = "__wvjb_queue_message__"; //oc調(diào)用js的回調(diào) var responseCallbacks = {}; //消息對(duì)應(yīng)的id var uniqueId = 1; //是否設(shè)置消息超時(shí) var dispatchMessagesWithTimeoutSafety = true; //web端注冊(cè)一個(gè)消息方法 function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler; } //web端調(diào)用一個(gè)OC注冊(cè)的消息 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; } //把消息轉(zhuǎn)換成JSON字符串返回 function _fetchQueue() { var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; return messageQueueString; } //OC調(diào)用JS的入口方法 function _handleMessageFromObjC(messageJSON) { _dispatchMessageFromObjC(messageJSON); } //初始化橋接對(duì)象,OC可以通過(guò)WebViewJavascriptBridge來(lái)調(diào)用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; //回調(diào) if (message.responseId) { responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else {//主動(dòng)調(diào)用 if (message.callbackId) { var callbackResponseId = message.callbackId; responseCallback = function(responseData) { _doSend({ handlerName: message.handlerName, responseId: callbackResponseId, responseData: responseData }); }; } //獲取JS注冊(cè)的函數(shù) var handler = messageHandlers[message.handlerName]; if (!handler) { console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message); } else { //調(diào)用JS中的對(duì)應(yīng)函數(shù)處理 handler(message.data, responseCallback); } } } } //把消息從JS發(fā)送到OC,執(zhí)行具體的發(fā)送操作。 function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = "cb_" + (uniqueId++) + "_" + new Date().getTime(); //存儲(chǔ)消息的回調(diào)ID responseCallbacks[callbackId] = responseCallback; //把消息對(duì)應(yīng)的回調(diào)ID和消息一起發(fā)送,以供消息返回以后使用。 message["callbackId"] = callbackId; } //把消息放入消息列表 sendMessageQueue.push(message); //下面這句話會(huì)出發(fā)JS對(duì)OC的調(diào)用 //讓webview執(zhí)行跳轉(zhuǎn)操作,從而可以在 //webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 中攔截到JS發(fā)給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); //注冊(cè)_disableJavascriptAlertBoxSafetyTimeout方法,讓OC可以關(guān)閉回調(diào)超時(shí),默認(rèn)是開啟的。 registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout); //執(zhí)行_callWVJBCallbacks方法 setTimeout(_callWVJBCallbacks, 0); //初始化WEB中注冊(cè)的方法。這個(gè)方法會(huì)把WEB中的hander注冊(cè)到bridge中。 //下面的代碼其實(shí)就是執(zhí)行WEB中的callback函數(shù)。 function _callWVJBCallbacks() { var callbacks = window.WVJBCallbacks; delete window.WVJBCallbacks; for (var i = 0; i < callbacks.length; i++) { callbacks[i](WebViewJavascriptBridge); } } })();
這里著重講 JS如何調(diào)native方法的。
window.WebViewJavascriptBridge = {
registerHandler: registerHandler, callHandler: callHandler, disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout, _fetchQueue: _fetchQueue, _handleMessageFromObjC: _handleMessageFromObjC };
這個(gè)對(duì)象就是上文中回調(diào)函數(shù)獲取到的WebViewJavascriptBridge 對(duì)象。callHandler表示執(zhí)行某個(gè)約定的方法。
上面的源碼注釋很清晰了,源碼就不過(guò)多解讀了。接下來(lái)我們舉一個(gè)完整的列子來(lái)闡述整個(gè)過(guò)程。
重點(diǎn)
getUserData => 調(diào)用setupWebViewJavascriptBridge => 因?yàn)槭堑谝淮握{(diào)用進(jìn)行初始化,會(huì)將回調(diào)函數(shù)保存到WVJBCallbacks中。
=> 動(dòng)態(tài)創(chuàng)建ifram,native截獲,注入代碼進(jìn)行brige初始化 => WebViewJavascriptBridge初始化 => 第一次執(zhí)行所以會(huì)觸發(fā)
_callWVJBCallbacks,遍歷上面的WVJBCallbacks數(shù)組,并且將WebViewJavascriptBridge作為參數(shù)傳入,執(zhí)行回調(diào)。 => 回調(diào)中執(zhí)行的就是getUserData里面的WebViewJavascriptBridge.callHandler("xxxx")
=》 callHandler執(zhí)行的其實(shí)就是__doSend方法。 =》 _doSend里面會(huì)將getUserData里面的回調(diào)函數(shù)保存在全局對(duì)象變量responseCallbacks中,key則是自增的ID。并且把getUserData所調(diào)用的方法名,參數(shù),key,都放在一個(gè)對(duì)象中(message),并將這個(gè)
message,存到另外一個(gè)全局?jǐn)?shù)組變量sendMessageQueue中。 => 動(dòng)態(tài)創(chuàng)建一個(gè)ifram,src為__wvjb_queue_message__,也就是說(shuō)創(chuàng)建的ifram,一般就兩種地址,一個(gè)是告訴native進(jìn)行初始化,一個(gè)是告訴native可以輪詢消息隊(duì)列sendMessageQueue了。 => native攔截到URL,遍歷全局變量sendMessageQueue,執(zhí)行g(shù)etUserData所需要的方法,這里就是XXXX,并組裝參數(shù) => 調(diào)用另一個(gè)_handleMessageFromObjC方法,解析參數(shù),得到一開始的自增ID,從全局responseCallbacks中取到真正的回調(diào)函數(shù)執(zhí)行。 => over了。完整的交互就是這個(gè)樣子的了。
上面的流程已經(jīng)很清楚了,如果有不懂,或者有錯(cuò)誤的地方歡迎指正。
這是js到native端的消息過(guò)程,還有native調(diào)js的,其實(shí)過(guò)程差不多。參考下文把
WebViewJavascriptBridge原理解析
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/109047.html
摘要:通訊狀態(tài)的改變次數(shù)被調(diào)用次數(shù)預(yù)計(jì)驗(yàn)證證實(shí)完畢。既然是因?yàn)檎{(diào)用間隔太短,所以就采用了切圖仔常用的節(jié)流方案。隨便說(shuō)說(shuō)縱觀整個(gè)通訊過(guò)程,其實(shí)就是一個(gè)網(wǎng)絡(luò)協(xié)議的縮影。前提-出現(xiàn)場(chǎng)景 使用機(jī)型為 Android 9,API 28 使用的 jsBridge 為 link bug 描述 在頁(yè)面加載前后如果連續(xù)多次調(diào)用原生的方法,會(huì)遇到回調(diào)參數(shù)未被調(diào)用的情況。 // 多次調(diào)用如下函數(shù), 部分 callb...
摘要:否則按照正常流程處理。如果是表示是初始化環(huán)境的消息,如果是則表示是發(fā)送消息。則立即發(fā)送消息。回調(diào)主動(dòng)調(diào)用獲取注冊(cè)的函數(shù)調(diào)用中的對(duì)應(yīng)函數(shù)處理把消息從發(fā)送到,執(zhí)行具體的發(fā)送操作。處理從返回的消息。從而找到具體的實(shí)現(xiàn)執(zhí)行。 基本說(shuō)明 我們的項(xiàng)目是一個(gè)OC與javascript重度交互的app,OC與javascript交互的那部分是在WebViewJavascriptBridge的githu...
摘要:否則按照正常流程處理。如果是表示是初始化環(huán)境的消息,如果是則表示是發(fā)送消息。則立即發(fā)送消息?;卣{(diào)主動(dòng)調(diào)用獲取注冊(cè)的函數(shù)調(diào)用中的對(duì)應(yīng)函數(shù)處理把消息從發(fā)送到,執(zhí)行具體的發(fā)送操作。處理從返回的消息。從而找到具體的實(shí)現(xiàn)執(zhí)行。 基本說(shuō)明 我們的項(xiàng)目是一個(gè)OC與javascript重度交互的app,OC與javascript交互的那部分是在WebViewJavascriptBridge的githu...
摘要:否則按照正常流程處理。如果是表示是初始化環(huán)境的消息,如果是則表示是發(fā)送消息。則立即發(fā)送消息?;卣{(diào)主動(dòng)調(diào)用獲取注冊(cè)的函數(shù)調(diào)用中的對(duì)應(yīng)函數(shù)處理把消息從發(fā)送到,執(zhí)行具體的發(fā)送操作。處理從返回的消息。從而找到具體的實(shí)現(xiàn)執(zhí)行。 基本說(shuō)明 我們的項(xiàng)目是一個(gè)OC與javascript重度交互的app,OC與javascript交互的那部分是在WebViewJavascriptBridge的githu...
閱讀 1869·2021-11-24 09:39
閱讀 3815·2021-09-29 09:47
閱讀 1722·2021-09-29 09:34
閱讀 3243·2021-09-10 10:51
閱讀 2707·2019-08-30 15:54
閱讀 3362·2019-08-30 15:54
閱讀 1010·2019-08-30 11:07
閱讀 1171·2019-08-29 18:36