一篇文章學(xué)會(huì)jsBridge的運(yùn)行機(jī)制
我司的APP是一個(gè)典型的混合開發(fā)APP,內(nèi)嵌的都是前端頁面,前端頁面要做到和原生的效果相似,就避免不了調(diào)用一些原生的方法,jsBridge
就是js
和原生
通信的橋梁,本文不講概念性的東西,而是通過分析一下我司項(xiàng)目中的jsBridge
源碼,來從前端角度大概了解一下它是怎么實(shí)現(xiàn)的。
js調(diào)用方式
先來看一下,js
是怎么來調(diào)用某個(gè)原生方法的,首先初始化的時(shí)候會(huì)調(diào)用window.WebViewJavascriptBridge.init
方法:
window.WebViewJavascriptBridge.init()
然后如果要調(diào)用某個(gè)原生方法可以使用下面的函數(shù):
function native (funcName, args = {}, callbackFunc, errorCallbackFunc) { // 校驗(yàn)參數(shù)是否合法 if (args && typeof args === 'object' && Object.prototype.toString.call(args).toLowerCase() === '[object object]' && !args.length) { args = JSON.stringify(args); } else { throw new Error('args不符合規(guī)范'); } // 判斷是否是手機(jī)環(huán)境 if (getIsMobile()) { // 調(diào)用window.WebViewJavascriptBridge對(duì)象的callHandler方法 window.WebViewJavascriptBridge.callHandler( funcName, args, (res) => { res = JSON.parse(res); if (res.code === 0) { return callbackFunc(res); } else { return errorCallbackFunc(res); } } ); } }
傳入要調(diào)用的方法名、參數(shù)和回調(diào)即可,它先校驗(yàn)了一下參數(shù),然后會(huì)調(diào)用window.WebViewJavascriptBridge.callHandler
方法。
此外也可以提供回調(diào)供原生調(diào)用:
window.WebViewJavascriptBridge.registerHandler(funcName, callbackFunc);
接下來看一下window.WebViewJavascriptBridge
對(duì)象到底是啥。
安卓
WebViewJavascriptBridge.js
文件內(nèi)是一個(gè)自執(zhí)行函數(shù),首先定義了一些變量:
// 定義變量 var messagingIframe; var sendMessageQueue = [];// 發(fā)送消息的隊(duì)列 var receiveMessageQueue = [];// 接收消息的隊(duì)列 var messageHandlers = {};// 消息處理器 var CUSTOM_PROTOCOL_SCHEME = 'yy';// 自定義協(xié)議 var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'; var responseCallbacks = {};// 響應(yīng)的回調(diào) var uniqueId = 1;
根據(jù)變量名簡單翻譯了一下,具體用處接下來會(huì)分析。接下來定義了WebViewJavascriptBridge
對(duì)象:
var WebViewJavascriptBridge = window.WebViewJavascriptBridge = { init: init, send: send, registerHandler: registerHandler, callHandler: callHandler, _fetchQueue: _fetchQueue, _handleMessageFromNative: _handleMessageFromNative };
可以看到就是一個(gè)普通的對(duì)象,上面掛載了一些方法,具體方法暫時(shí)不看,繼續(xù)往下:
var doc = document; _createQueueReadyIframe(doc);
調(diào)用了_createQueueReadyIframe
方法:
function _createQueueReadyIframe (doc) { messagingIframe = doc.createElement('iframe'); messagingIframe.style.display = 'none'; doc.documentElement.appendChild(messagingIframe); }
這個(gè)方法很簡單,就是創(chuàng)建了一個(gè)隱藏的iframe
插入到頁面,繼續(xù)往下:
// 創(chuàng)建一個(gè)Events類型(基礎(chǔ)事件模塊)的事件(Event)對(duì)象 var readyEvent = doc.createEvent('Events'); // 定義事件名為WebViewJavascriptBridgeReady readyEvent.initEvent('WebViewJavascriptBridgeReady'); // 通過document來觸發(fā)該事件 doc.dispatchEvent(readyEvent);
這里定義了一個(gè)自定義事件,并直接派發(fā)了,其他地方可以像通過監(jiān)聽原生事件一樣監(jiān)聽該事件:
document.addEventListener( 'WebViewJavascriptBridgeReady', function () { console.log(window.WebViewJavascriptBridge) }, false );
這里的用處我理解就是當(dāng)該jsBridge
文件如果是在其他代碼之后引入的話需要保證之前的代碼能知道window.WebViewJavascriptBridge
對(duì)象何時(shí)可用,如果規(guī)定該jsBridge
必須要最先引入的話那么就不需要這個(gè)處理了。
到這里自執(zhí)行函數(shù)就結(jié)束了,接下來看一下最開始的init
方法:
function init (messageHandler) { if (WebViewJavascriptBridge._messageHandler) { throw new Error('WebViewJavascriptBridge.init called twice'); } // init調(diào)用的時(shí)候沒有傳參,所以messageHandler=undefined WebViewJavascriptBridge._messageHandler = messageHandler; // 當(dāng)前receiveMessageQueue也只是一個(gè)空數(shù)組 var receivedMessages = receiveMessageQueue; receiveMessageQueue = null; for (var i = 0; i < receivedMessages.length; i++) { _dispatchMessageFromNative(receivedMessages[i]); } }
從初始化的角度來看,這個(gè)init
方法似乎啥也沒做。接下來我們來看callHandler
方法,看看是如何調(diào)用安卓的方法的:
function callHandler (handlerName, data, responseCallback) { _doSend({ handlerName: handlerName, data: data }, responseCallback); }
處理了一下參數(shù)又調(diào)用了_doSend
方法:
function _doSend (message, responseCallback) { // 如果提供了回調(diào)的話 if (responseCallback) { // 生成一個(gè)唯一的回調(diào)id var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); // 回調(diào)通過id存儲(chǔ)到responseCallbacks對(duì)象上 responseCallbacks[callbackId] = responseCallback; // 把該回調(diào)id添加到要發(fā)送給native的消息里 message.callbackId = callbackId; } // 消息添加到消息隊(duì)列里 sendMessageQueue.push(message); messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
這個(gè)方法首先把調(diào)用原生方法時(shí)的回調(diào)函數(shù)通過生成一個(gè)唯一的id
保存到最開始定義的responseCallbacks
對(duì)象里,然后把該id
添加到要發(fā)送的信息上,所以一個(gè)message
的結(jié)構(gòu)是這樣的:
{ handlerName, data, callbackId }
接著把該message
添加到最開始定義的sendMessageQueue
數(shù)組里,最后設(shè)置了iframe
的src
屬性:yy://__QUEUE_MESSAGE__/
,這其實(shí)就是一個(gè)自定義協(xié)議的url
,我簡單搜索了一下,native
會(huì)攔截這個(gè)url
來做相應(yīng)的處理,到這里我們就走不下去了,因?yàn)椴恢涝隽耸裁词虑?,簡單搜索了一下,發(fā)現(xiàn)了這個(gè)庫:WebViewJavascriptBridge,我司應(yīng)該是在這個(gè)庫基礎(chǔ)上修改的,結(jié)合了網(wǎng)上的一些文章后大概知道了,原生攔截到這個(gè)url
后會(huì)調(diào)用js
的window.WebViewJavascriptBridge._fetchQueue
方法:
function _fetchQueue () { // 把我們要發(fā)送的消息隊(duì)列轉(zhuǎn)成字符串 var messageQueueString = JSON.stringify(sendMessageQueue); // 清空消息隊(duì)列 sendMessageQueue = []; // 安卓無法直接讀取返回的數(shù)據(jù),因此還是通過iframe的src和java通信 messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); }
安卓攔截到url
后,知道js
給安卓發(fā)送消息了,所以主動(dòng)調(diào)用js
的_fetchQueue
方法,取出之前添加到隊(duì)列里的消息,因?yàn)闊o法直接讀取js
方法返回的數(shù)據(jù),所以把格式化后的消息添加到url
上,再次通過iframe
來發(fā)送,此時(shí)原生又會(huì)攔截到yy://return/_fetchQueue/
這個(gè)url
,那么取出后面的消息,解析出要其中要執(zhí)行的原生方法名和參數(shù)后執(zhí)行對(duì)應(yīng)的原生方法,當(dāng)原生方法執(zhí)行完后又會(huì)主動(dòng)調(diào)用js
的window.WebViewJavascriptBridge._handleMessageFromNative
方法:
function _handleMessageFromNative (messageJSON) { // 根據(jù)之前的init方法的邏輯我們知道receiveMessageQueue是會(huì)被設(shè)置為null的,所以會(huì)走else分支 if (receiveMessageQueue) { receiveMessageQueue.push(messageJSON); } else { _dispatchMessageFromNative(messageJSON); } }
看一下_dispatchMessageFromNative
方法做了什么:
function _dispatchMessageFromNative (messageJSON) { setTimeout(function () { // 原生發(fā)回的消息是字符串類型的,轉(zhuǎn)成json var message = JSON.parse(messageJSON); var responseCallback; // java調(diào)用完成,發(fā)回的responseId就是我們之前發(fā)送給它的callbackId if (message.responseId) { // 從responseCallbacks對(duì)象里取出該id關(guān)聯(lián)的回調(diào)方法 responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } // 執(zhí)行回調(diào),js調(diào)用安卓方法后到這里順利收到消息 responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else { // ... } }); }
messageJSON
就是原生發(fā)回的消息,里面除了執(zhí)行完原生方法后返回的相關(guān)信息外,還帶著之前我們傳給它的callbackId
,所以我們可以通過這個(gè)id
來在responseCallbacks
里找到關(guān)聯(lián)的回調(diào)并執(zhí)行,本次js
調(diào)用原生方法流程結(jié)束。但是,明顯函數(shù)里還有不存在id
時(shí)的分支,這里是用來干啥的呢,我們前面介紹的都是js
調(diào)用原生方法,但是顯然,原生也可以直接給js
發(fā)消息,比如常見的攔截返回鍵功能,當(dāng)原生監(jiān)聽到返回鍵事件后它會(huì)主動(dòng)發(fā)送信息告訴前端頁面,頁面就可以執(zhí)行對(duì)應(yīng)的邏輯,這個(gè)else
分支就是用來處理這種情況:
function _dispatchMessageFromNative (messageJSON) { setTimeout(function () { if (message.responseId) { // ... } else { // 和我們傳給原生的消息可以帶id一樣,原生傳給我們的消息也可以帶一個(gè)id,同時(shí)原生內(nèi)部也會(huì)通過這個(gè)id關(guān)聯(lián)一個(gè)回調(diào) if (message.callbackId) { var callbackResponseId = message.callbackId; //如果前端需要再給原生回消息的話那么就帶上原生之前傳來的id,這樣原生就可以通過id找到對(duì)應(yīng)的回調(diào)并執(zhí)行 responseCallback = function (responseData) { _doSend({ responseId: callbackResponseId, responseData: responseData }); }; } // 我們并沒有設(shè)置默認(rèn)的_messageHandler,所以是undefined var handler = WebViewJavascriptBridge._messageHandler; // 原生發(fā)送的消息里面有處理方法名稱 if (message.handlerName) { // 通過方法名稱去messageHandlers對(duì)象里查找是否有對(duì)應(yīng)的處理方法 handler = messageHandlers[message.handlerName]; } try { // 執(zhí)行處理方法 handler(message.data, responseCallback); } catch (exception) { if (typeof console !== 'undefined') { console.log('WebViewJavascriptBridge: WARNING: javascript handler threw.', message, exception); } } } }); }
比如我們要監(jiān)聽原生的返回鍵事件,我們先通過window.WebViewJavascriptBridge
對(duì)象的方法注冊一下:
window.WebViewJavascriptBridge.registerHandler('onBackPressed', () => { // 做點(diǎn)什么... })
registerHandler
方法如下:
function registerHandler (handlerName, handler) { messageHandlers[handlerName] = handler; }
很簡單,把我們要監(jiān)聽的事件名和方法都存儲(chǔ)到messageHandlers
對(duì)象上,然后如果原生監(jiān)聽到返回鍵事件后會(huì)發(fā)送如下結(jié)構(gòu)的消息:
{ handlerName: 'onBackPressed' }
這樣就可以通過handlerName
找到我們注冊的函數(shù)進(jìn)行執(zhí)行了。
到此,安卓環(huán)境的js
和原生互相調(diào)用的邏輯就結(jié)束了,總結(jié)一下就是:
1.js調(diào)用原生
生成一個(gè)唯一的id
,把回調(diào)和id
保存起來,然后將要發(fā)送的信息(帶上本次生成的唯一id)添加到一個(gè)隊(duì)列里,之后通過iframe
發(fā)送一個(gè)自定義協(xié)議的請(qǐng)求,原生攔截到后調(diào)用js
的window.WebViewJavascriptBridge
對(duì)象的一個(gè)方法來獲取隊(duì)列的信息,解析出請(qǐng)求和參數(shù)后執(zhí)行對(duì)應(yīng)的原生方法,然后再把響應(yīng)(帶上前端傳來的id)通過調(diào)用js
的window.WebViewJavascriptBridge
的指定方法傳遞給前端,前端再通過id
找到之前存儲(chǔ)的回調(diào),進(jìn)行執(zhí)行。
2.原生調(diào)用js
首先前端需要事先注冊要監(jiān)聽的事件,把事件名和回調(diào)保存起來,然后原生在某個(gè)時(shí)刻會(huì)調(diào)用js
的window.WebViewJavascriptBridge
對(duì)象的指定方法,前端根據(jù)返回參數(shù)的事件名找到注冊的回調(diào)進(jìn)行執(zhí)行,同時(shí)原生也會(huì)傳過來一個(gè)id
,如果前端執(zhí)行完相應(yīng)邏輯后還要給原生回消息,那么要把該id
帶回去,原生根據(jù)該id
來找到對(duì)應(yīng)的回調(diào)進(jìn)行執(zhí)行。
可以看到,js
和原生兩邊的邏輯都是一致的。
ios
ios
和安卓基本是一致的,部分細(xì)節(jié)上有點(diǎn)區(qū)別,首先是協(xié)議不一樣,ios
的是這樣的:
var CUSTOM_PROTOCOL_SCHEME_IOS = 'https'; var QUEUE_HAS_MESSAGE_IOS = '__wvjb_queue_message__';
然后ios
初始化創(chuàng)建iframe
的時(shí)候會(huì)發(fā)送一個(gè)請(qǐng)求:
var BRIDGE_LOADED_IOS = '__bridge_loaded__'; function _createQueueReadyIframe (doc) { messagingIframe = doc.createElement('iframe'); messagingIframe.style.display = 'none'; if (isIphone()) { // 這里應(yīng)該是ios需要先加載一下bridge messagingIframe.src = CUSTOM_PROTOCOL_SCHEME_IOS + '://' + BRIDGE_LOADED_IOS; } doc.documentElement.appendChild(messagingIframe); }
再然后是ios
獲取我們的消息隊(duì)列時(shí)不需要通過iframe
,它能直接獲取執(zhí)行js
函數(shù)返回的數(shù)據(jù):
function _fetchQueue () { var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; return messageQueueString;// 直接返回,不需要通過iframe }
其他部分都是一樣的。
總結(jié)
本文分析了一下jsBridge
的源碼,可以發(fā)現(xiàn)其實(shí)是個(gè)很簡單的東西,但是平時(shí)可能就沒有去認(rèn)真了解過它,總想做一些”大“的事情,以至于淪為了一個(gè)”好高騖遠(yuǎn)“的人,希望各位不要像筆者一樣。
到此這篇關(guān)于一篇文章學(xué)會(huì)jsBridge的運(yùn)行機(jī)制的文章就介紹到這了,更多相關(guān)jsBridge 運(yùn)行機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 微信瀏覽器內(nèi)置JavaScript對(duì)象WeixinJSBridge使用實(shí)例
- 微信內(nèi)置瀏覽器私有接口WeixinJSBridge介紹
- 微信WeixinJSBridge API使用實(shí)例
- Android中極簡的js與java的交互庫(SimpleJavaJsBridge)
- iOS開發(fā)之WKWebViewJavascriptBridge Xcode9中導(dǎo)致crash的解決
- android和js的交互之jsbridge使用教程
- Flutter使用JsBridge方式處理Webview與H5通信的方法
- 如何通過Proxy實(shí)現(xiàn)JSBridge模塊化封裝
- Javascript之JSBridge初探
- JS中bridge的原理與封裝
相關(guān)文章
codemirror6實(shí)現(xiàn)在線代碼編輯器使用詳解
這篇文章主要為大家介紹了codemirror6實(shí)現(xiàn)在線代碼編輯器使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01web?worker在項(xiàng)目中的使用學(xué)習(xí)為項(xiàng)目增加亮點(diǎn)
這篇文章主要為大家介紹了web?worker使用學(xué)習(xí)來為你的項(xiàng)目增加亮點(diǎn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07微信小程序 (一)新建項(xiàng)目hello WeApp 詳細(xì)介紹
這篇文章主要介紹了微信小程序 (一)hello WeApp 詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-09-09微信小程序?qū)崿F(xiàn)給循環(huán)列表添加點(diǎn)擊樣式實(shí)例
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)給循環(huán)列表添加點(diǎn)擊樣式實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-04-04JS前端錯(cuò)誤監(jiān)控捕獲以及上報(bào)方法詳解
這篇文章主要為大家介紹了前端錯(cuò)誤監(jiān)控捕獲以及上報(bào)方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01腳本整合指定文件/文件夾執(zhí)行定制化ESLint命令使用實(shí)例
這篇文章主要為大家介紹了腳本整合指定文件/文件夾執(zhí)行定制化?ESLint命令使用實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11mini?webpack打包基礎(chǔ)解決包緩存和環(huán)依賴
這篇文章主要為大家介紹了mini?webpack打包基礎(chǔ)解決包緩存和環(huán)依賴示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09