//notation: js file can only use this kind of comments
//since comments will cause error when use in webview.loadurl,
//comments will be remove by java use regexp
(function() {
if (window.WebViewJavascriptBridge) {
return;
}
var messagingIframe;
var bizMessagingIframe;
var sendMessageQueue = [];
var receiveMessageQueue = [];
var messageHandlers = {};
var CUSTOM_PROTOCOL_SCHEME = 'yy';
var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/';
var responseCallbacks = {};
var uniqueId = 1;
// 創建消息index隊列iframe
function _createQueueReadyIframe(doc) {
messagingIframe = doc.createElement('iframe');
messagingIframe.style.display = 'none';
doc.documentElement.appendChild(messagingIframe);
}
//創建消息體隊列iframe
function _createQueueReadyIframe4biz(doc) {
bizMessagingIframe = doc.createElement('iframe');
bizMessagingIframe.style.display = 'none';
doc.documentElement.appendChild(bizMessagingIframe);
}
//set default messageHandler 初始化默認的消息線程
function init(messageHandler) {
if (WebViewJavascriptBridge._messageHandler) {
throw new Error('WebViewJavascriptBridge.init called twice');
}
WebViewJavascriptBridge._messageHandler = messageHandler;
var receivedMessages = receiveMessageQueue;
receiveMessageQueue = null;
for (var i = 0; i < receivedMessages.length; i++) {
_dispatchMessageFromNative(receivedMessages[i]);
}
}
// 發送
function send(data, responseCallback) {
_doSend({
data: data
}, responseCallback);
}
// 註冊線程 往數組裏面添加值
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
// 調用線程
function callHandler(handlerName, data, responseCallback) {
_doSend({
handlerName: handlerName,
data: data
}, responseCallback);
}
//sendMessage add message, 觸發native處理 sendMessage
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message.callbackId = callbackId;
}
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
// 提供給native調用,該函數作用:獲取sendMessageQueue返回給native,由於android不能直接獲取返回的內容,所以使用url shouldOverrideUrlLoading 的方式返回內容
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
//android can't read directly the return data, so we can reload iframe src to communicate with java
bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}
//提供給native使用,
function _dispatchMessageFromNative(messageJSON) {
setTimeout(function() {
var message = JSON.parse(messageJSON);
var responseCallback;
//java call finished, now need to call js callback function
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({
responseId: callbackResponseId,
responseData: responseData
});
};
}
var handler = WebViewJavascriptBridge._messageHandler;
if (message.handlerName) {
handler = messageHandlers[message.handlerName];
}
//查找指定handler
try {
handler(message.data, responseCallback);
} catch (exception) {
if (typeof console != 'undefined') {
console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
}
}
}
});
}
//提供給native調用,receiveMessageQueue 在會在頁面加載完後賦值為null,所以
function _handleMessageFromNative(messageJSON) {
console.log(messageJSON);
if (receiveMessageQueue) {
receiveMessageQueue.push(messageJSON);
}
_dispatchMessageFromNative(messageJSON);
}
var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
init: init,
send: send,
registerHandler: registerHandler,
callHandler: callHandler,
_fetchQueue: _fetchQueue,
_handleMessageFromNative: _handleMessageFromNative
};
var doc = document;
_createQueueReadyIframe(doc);
_createQueueReadyIframe4biz(doc);
var readyEvent = doc.createEvent('Events');
readyEvent.initEvent('WebViewJavascriptBridgeReady');
readyEvent.bridge = WebViewJavascriptBridge;
doc.dispatchEvent(readyEvent);
})();
待過的某家公司中用於和原生交互的方案,當初這個文件也是從網上找的,拿來就用,沒怎麼細看。
最早我們的Hybrid應用只是h5調用原生的一些方法,所以只使用了native向頁面注入一個webview的變量,並在這個變量上掛載一些方法,在安卓上存在一些問題(?安全漏洞)。後來改用bridge,安全性和兼容性比較好。bridge有一個小小的問題就是如果原生端不存在對應名稱的方法,h5無法知道,所以h5需要做一套版本的判斷,再進行方法的調用。
執行的操作
簡單分析一下:這個文件包含了一個立即執行函數,代碼加載後,做了幾件事情:
- 定義協議scheme,類似http,這裏定義為yy
- 創建消息index隊列iframe,這個iframe後面可以看到是h5端使用的;創建消息體隊列iframe,這個iframe可以看到是給原生端使用的。這兩個iframe用於消息傳遞。
- 創建WebViewJavaScriptBridge對象並掛載到window上
- 創建Event對象,初始化事件名為WebViewJavaScriptBridgeReady的事件,並觸發事件,表示bridge對象已準備好,可以使用了。
bridge對象
bridge對象有6個方法,分別為init、send、registerHandler、callHandler、_fetchQueue、_handleMessageFromNative,方法的作用大致可以見名知意。
- init方法,用於設置默認的消息處理函數,如果此時h5端的消息接收隊列不為空,則調用_dispatchMessageFromNative來分發處理隊列中的消息
- send方法,用於h5端發送消息,觸發native去處理,調用_doSend將消息塞進h5端的消息發送隊列,並將messagingIframe的src設置為發送的協議,觸發iframe重新請求加載
- registerHandler方法:用於定義消息名稱與消息回調函數的映射,保存在messageHandlers對象上,提供js函數給native調用,在原生完成操作後,將數據通過回調函數傳遞給h5
- callHandler方法:用於h5調用_doSend修改iframe src的方式將消息和數據傳遞原生
- _fetchQueue方法:提供給native使用的方法,將bizMessagingIframe的src設置為接收的協議,觸發iframe重新請求,使原生獲取到h5發送的消息
- _handleMessageFromNative方法:提供給native使用的方法,將消息塞進h5的消息接收隊列,並進行消息分發
大致流程
1. H5調用Native
2. Native向H5傳遞
原理
H5調用Native的流程主要利用了iframe與原生端進行交互,通過修改iframe的src來觸發原生端調用對應的操作,原生端監聽到觸發後,經過一系列操作後得到一個結果,再通過調用h5的回調函數,使h5可以處理原生返回的結果。
Native向h5傳遞有點類似h5的事件監聽,h5註冊一個名稱用於標記操作,映射一個回調函數,原生端在用户做了某些操作後,獲取到h5註冊的操作對應的回調函數,並進行調用,將操作結果傳遞給h5。
參考
https://github.com/lzyzsd/JsB...
luffyjet/WebViewJavaScriptBridge
marcuswestin/WebViewJavascriptBridge
https://blog.csdn.net/weixin_...
https://blog.csdn.net/sinat_3...
Android與JS交互篇--JSBridge的使用