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

