前言
项目中一直在使用JsBridge,看了下源码,做个记录。
本篇文章先介绍用法,然后进行源码分析,因为主要是Js层不了解,所以主要对Js做了分析。
1. Java 调用 Js
1.通过注册handlerName进行通信
// Js代码中注册
WebViewJavascriptBridge.registerHandler("JavaCallJs", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("从Java端接收的数据: = " + data);
responseCallback("Js端处理完了");
});
// Java代码中调用
mWebView.callHandler("JavaCallJs", "向Js端传入的数据", new CallBackFunction() {
@Override public void onCallBack(String data) {
// TODO Auto-generated method stub
Log.i(TAG, "Js端处理完后返回数据: " + data);
}
});
2.通过Js端DefaultHandler进行通信
// Js中通过init方法设置默认handler
bridge.init(function(message, responseCallback) {
console.log('JS got a message', message);
var data = {
'Javascript Responds': '测试中文!'
};
responseCallback("Js端处理完后返回数据: " + data);
});
//Java代码中调用
webView.send("hello", new CallBackFunction() {
@Override public void onCallBack(String data) {
Log.d("moren", data);
}
});
webView.send("hello");
2. Js 调用 Java
1.通过注册handlerName通信
// Java代码中
mWebView.registerHandler("JsCallJava", new BridgeHandler() {
@Override public void handler(String data, CallBackFunction function) {
Log.i(TAG, "Js端返回数据: " + data);
function.onCallBack("Java端处理完了");
}
});
//Js代码调用
WebViewJavascriptBridge.callHandler(
'JsCallJava', { 'param': '中文测试'},
function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
2.通过DefaultHandler进行通信
// Java端设置默认处理Handler
mWebView.setDefaultHandler(new DefaultHandler());
//Js端调用
WebViewJavascriptBridge.send(
data,
function(responseData) {
document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
}
);
3. 源码分析
该框架的主要分析在于对Js的理解。
/* * 在WebView#onPageFinished时候加载该Js。 */
(function() {
if (window.WebViewJavascriptBridge) {
return;
}
var messagingIframe; // 变更时会回调到WebView#shouldOverrideUrlLoading,此时根据制定好的规则进行分发
var sendMessageQueue = [];
var receiveMessageQueue = [];
var messageHandlers = {};
var CUSTOM_PROTOCOL_SCHEME = 'yy';
var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/';
var responseCallbacks = {};
var uniqueId = 1;
// 在加载时即创建该frame
function _createQueueReadyIframe(doc) {
messagingIframe = doc.createElement('iframe');
messagingIframe.style.display = 'none';
doc.documentElement.appendChild(messagingIframe);
}
// Java调用Js时,Js默认处理handler
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]);
}
}
// Js call Java
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, call Java
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来调用_fetchQueue
}
// 提供给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
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); //通过这种方式将数据传给native
}
//提供给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 {
//直接发送,Java中rigistHandler时会生成callbackId
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({
responseId: callbackResponseId,
responseData: responseData
});
};
}
var handler = WebViewJavascriptBridge._messageHandler;//先使用默认的handler
if (message.handlerName) {
// js中rigistHandler时会放进来
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.length > 0) {
receiveMessageQueue.push(messageJSON);
} else {
_dispatchMessageFromNative(messageJSON);
}
}
var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
init: init,
send: send,
registerHandler: registerHandler,
callHandler: callHandler,
_fetchQueue: _fetchQueue,
_handleMessageFromNative: _handleMessageFromNative
};
var doc = document;
_createQueueReadyIframe(doc);
var readyEvent = doc.createEvent('Events');
readyEvent.initEvent('WebViewJavascriptBridgeReady');
readyEvent.bridge = WebViewJavascriptBridge;
doc.dispatchEvent(readyEvent);
})();
4. 遇到的问题
所遇到的问题都是由iframe.src引起的:
1. iframe.src的url长度有大小限制,过大则直接会丢失
2. iframe.src的reload,即src重新复制,如果频率太快,则也会直接丢失
3. 解决完这两个问题后,在Java代码中,responseCallback这个HashMap里面的数据不用remove了,在handleReturnData里,因为上游延迟了发送。
我们解决第一个问题是将sendMessageQueue
分割发送,3个为一组,降低数据量。
解决第二个问题是 setTimeout
,延迟发送。发送每一组时有一个100 + Math.ramdom()*50
的间距。
这个问题类似于背压的问题,上游数据发送的太快,远远超出了iframe处理速度,所以做了个类似的buffer操作。
还有更好的解决方案:数据的变化放在window的一个对象里,直接注入JS获取。
2017/08/09,最终发现使用iframe弊端太多,timeout跟随机器的不同而不同。
于是抛弃使用iframe通信方式,使用alert来传输数据。
更改bug后的代码见GitHub#JsBridge。