JsBridge的概念
JsBridge是用来实现native和js交互通信的中间件。
H5与native交互,本质上来说就两种调用:
- JavaScript 调用 native 方法,
- native 调用 JavaScript 方法。
JavaScript调用native方法有两种方式:
- 注入,native 往 webview 的 window 对象中添加一些原生方法,h5可以通过注入的方法来调用 app 的原生能力
- 拦截,H5通过与 native 之间的协议发送请求,nativ e拦截请求再去调用 app 原生能力
注入
在android的实现:
public class WebAppInterface {
// 供Js调用的方法要加JavascriptInterface注解
@JavascriptInterface
public int double(value) {
return value * 2
}
@JavascriptInterface
public int triple(value) {
return value * 3
}
}
webView.addJavascriptInterface(new WebAppInterface(), "android")
在JavaScript中调用
window.android.double(10) // 20
这种办法比较普遍,但是为了让JavaScript跟IOS也能通信,一般不推荐这种方式。
拦截
拦截的方式是通过拦截URL,解析约定的协议后调用原生方法,缺点是结束协议比较繁琐,传回执行之后的返回值也比较麻烦。
在H5端中,主要是通过iframe.src去发送一个url请求,这个请求是H5和native约定好的特殊协议,比如约定为yy://。
在android中主要是通过shouldOverrideUrlLoading()方法来拦截URL(shouldOverrideUrlLoading()可以拦截所有webView的跳转)。如果发现URL是约定好的协议开头,那么native端就能确定H5是在试图调用原生的方法,通过解析URL中的数据去调用相关的原生能力。
native调用JavaScript方法有两种方式:
- loadUrl,直接使用
WebView.loadUrl("javascript:function()")方法 - 拦截,同上
loadUrl
直接调用即可做到native调用H5
webView.loadUrl("javascript:function()");
JsBridge实现原理+源码解析
由于公司android app的JsBridge用的是github上开源的项目,所以就用这个项目去了解JaBridge是如何实现的。
在JavaScript中使用
function setupWebViewJavascriptBridge(callback) {
// 已经注册了WebViewJavascriptBridge,就执行回调
if (window.WebViewJavascriptBridge) {
return callback(WebViewJavascriptBridge);
} else {
// WebViewJavascriptBridgeReady 事件是在 JsBridge 注册的
document.addEventListener('WebViewJavascriptBridgeReady', function () {
// WebView 加载完成后初始化 JsBridge
// WebViewJavascriptBridge 也是在 JsBridge 定义的
callback(WebViewJavascriptBridge)
}, false);
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback);
}
window.WVJBCallbacks = [callback];
// 创建一个iframe 设置不可见并将src设置为 https://__bridge_loaded__
// 设置在下一次宏任务中移除这个 iframe
// 这里创建 iframe 的目的是为了让 webview 拦截到这个请求并进行特定的操作。主要用于IOS的WKWebview
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'https://__bridge_loaded__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function () {
document.documentElement.removeChild(WVJBIframe)
}, 0)
}
}
//初始化
setupWebViewJavascriptBridge(function (bridge) {
try {
bridge.init(function (message, callback) {
callback(null);
})
} catch (e) { }
});
export default {
//js调APP方法 (参数分别为:app提供的方法名 传给app的数据 回调)
callhandler: function (method, params, callback) {
setupWebViewJavascriptBridge(function (bridge) {
bridge.callHandler(method, params, callback)
})
},
// APP调js方法 (参数分别为:js提供的方法名 回调)
registerHandler:function (method, callback) {
setupWebViewJavascriptBridge((bridge) => {
bridge.registerHandler(method, (data, responseCallback) => {
callback(data, responseCallback)
})
})
}
}
JsBridge 源码解析
WebViewJavaScriptBridge.js源码
// 自执行函数
(function(){
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;
}
// 调用线程,H5中可使用bridge的方法
function callHandler(handlerName, data, responseCallback) {
_doSend({
handlerName: handlerName,
data: data
}, responseCallback);
}
//sendMessage add message, 触发native处理 sendMessage
function _doSend(message, responseCallback) {
// 如果有回调函数,就往 responseCallback 添加该回调函数
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message.callbackId = callbackId;
}
sendMessageQueue.push(message);
// 修改 messagingIframe 的src 触发 shouldOverrideUrlLoading 方法
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;
// 解析responseData 为 Object
try {
message.responseData = JSON.parse(message.responseData);
} catch(exception) {
if (typeof console != 'undefined') {
console.log("WebViewJavascriptBridge: ERROR: json parse threw.", message, exception);
}
}
//java call finished, now need to call js callback function
// android原生方法已经执行完毕,如果有回调函数就通过 responseId 在
// responseCallbacks 中找到该方法并执行
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
// 直接发送
// 这里为native调用js的情况
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);
}
}
}
}, 0);
}
//提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以
function _handleMessageFromNative(messageJSON) {
if (receiveMessageQueue) {
receiveMessageQueue.push(messageJSON);
}
_dispatchMessageFromNative(messageJSON);
}
// 在window上注册一个WebViewJavascriptBridge属性供H5使用
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);
})()
BridgeWebViewClient.kt
// webView页面加载完毕后就加载执行WebViewJavascriptBridge.js文件
override fun onPageFinished() {
....
if (BridgeWebViewKotlin.toLoadJs != null) {
BridgeUtil.webViewLoadLocalJs(view!!, BridgeWebViewKotlin.toLoadJs)
}
}
// 通过 shouldOverrideUrlLoading 方法去监听iframe的src的变化
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
Log.d(TAG, "shouldOverrideUrlLoading api24")
loadStart = false
currentUrl = request.url.toString()
try {
currentUrl = URLDecoder.decode(currentUrl, "UTF-8")
} catch (ex: UnsupportedEncodingException) {
ex.printStackTrace()
}
if (currentUrl!!.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据
// webView.flushMessageQueue() 方法执行完之后会执行 webView.handlerReturnData(currentUrl!!)
// 执行原生方法
webView.handlerReturnData(currentUrl!!)
// 返回 true 证明这个 webview 不会再加载这个链接了
return true
} else if (currentUrl!!.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
// 主要的作用是将 callback 添加到 responseCallbacks
// 并且修改 bizMessagingIframe 的src 重新触发 shouldOverrideUrlLoading
webView.flushMessageQueue()
return true
} else {
// 加载这个新的链接
return if (proxy != null) {
proxy!!.shouldOverrideUrlLoading(view, request)
} else {
super.shouldOverrideUrlLoading(view, request)
}
}
}
BridgeWebViewKotlin.kt
/**
* 刷新消息队列
*/
fun flushMessageQueue() {
if (Thread.currentThread() === Looper.getMainLooper().thread) {
// 执行 WebViewJavascriptBridge.js 中的 _fetchQueue 方法
loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, object : CallBackFunction {
override fun onCallBack(data: String?) {
// deserializeMessage 反序列化消息
var list: List<Message>?
try {
list = Message.toArrayList(data)
} catch (e: Exception) {
e.printStackTrace()
return
}
if (list.isEmpty()) {
return
}
for (i in list.indices) {
val m = list[i]
val responseId = m.responseId
// 是否是response CallBackFunction
if (!TextUtils.isEmpty(responseId)) {
val function = responseCallbacks[responseId]
val responseData = m.responseData
function!!.onCallBack(responseData)
responseCallbacks.remove(responseId)
} else {
var responseFunction: CallBackFunction? = null;
// if had callbackId 如果有回调Id
val callbackId = m.callbackId
if (!TextUtils.isEmpty(callbackId)) {
responseFunction = object : CallBackFunction {
// 调用完原生方法调用onCallBack方法执行js的回调函数
override fun onCallBack(data: String?) {
val responseMsg = Message()
responseMsg.responseId = callbackId
responseMsg.responseData = data
queueMessage(responseMsg)
}
}
} else {
responseFunction = object : CallBackFunction {
override fun onCallBack(data: String?) {
//nothing to do
}
}
}
// BridgeHandler执行
var handler: BridgeHandler? = if (!TextUtils.isEmpty(m.handlerName)) {
messageHandlers!![m.handlerName]
} else {
defaultHandler
}
handler?.handler(m.data, responseFunction)
}
}
}
})
}
}
详细的调用流程图为: