Android Native与Web JS双向通信框架-PieBridge
Android Native与Web JS双向通信框架
一、框架流程图
二、支持的功能点
1、支持Android Native调用JS中注册的方法,支持JS层数据的Callback回调。
2、支持JS调用Android Native中注册的方法,支持Native层数据的Callback回调。
3、数据的传递支持序列化后JSON格式。
三、前期的初始化准备
- 1、在Android中的assets目录放入PieBridge.js, 本地HTML模板中引入PieBridge.js
- 2、在Android中初始化WebView和PieBridge,并加载本地HTML模板
private val mPieBridge: PieBridge by lazy {
PieBridge(BridgeDefaultWebView(webview))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPieBridge.loadDataWithBaseURL("file:///android_asset/bridge/", loadLocalHtml(), "text/html", "utf-8", null)
val json = "{\"dataType\": \"Banner\",\"id\": 1299,\"title\": \"训练营\"}"
mPieBridge.executeHandler("functionInJs", json, object : IHandlerCallback {
override fun onCallback(data: String) {
val jsonObject = JSONObject(data)
Toast.makeText(this@MainActivity, "来自Js的数据: ${jsonObject.getStringValue("id")}", Toast.LENGTH_SHORT).show()
}
})
}
private fun loadLocalHtml(): String {
try {
return assets.open("bridge/index.html").reader().readText()
} catch (e: Exception) {
e.printStackTrace()
}
return ""
}
override fun onDestroy() {
super.onDestroy()
mPieBridge.release()
}
四、Android Native 调用JS使用步骤
- 1、先在JS层中往PieBridge中注册一个Handler,
window.PieBridge.registerHandler
window.PieBridge.registerHandler('functionInJs', function(data, responseCallback) {
document.querySelector('body').innerHTML = `data from Java: = ${data}`;
var jsonObject = {"dataType": "Banner","id": 1299,"title": "训练营"}
if (responseCallback) {
responseCallback(JSON.stringify(jsonObject));
}
});
- 2、然后直接在Android中调用
mPieBridge.executeHandler
即可。
mPieBridge.executeHandler("functionInJs", json, object : IHandlerCallback {
override fun onCallback(data: String) {
val jsonObject = JSONObject(data)
Toast.makeText(this@MainActivity, "来自Js的数据: ${jsonObject.getStringValue("id")}", Toast.LENGTH_SHORT).show()
}
})
五、JS调用Android Native使用步骤
- 1、先在Android Native层中往PieBridge中注册一个Handler,
window.PieBridge.registerHandler
mPieBridge.registerHandler("AndroidInJs", object : IMessageHandler {
override fun handle(data: String, callback: IHandlerCallback?) {
Toast.makeText(this@MainActivity, "来自Js层传递的数据: $data", Toast.LENGTH_SHORT).show()
callback?.onCallback("回调给JS层的方法")
}
})
- 2、然后直接在JS层中调用
window.PieBridge.callHandler
即可。
window.PieBridge.callHandler('AndroidInJs', "JS层的数据", function(data, responseCallback) {
console.log("来自Android Native层回调数据")
var jsonObject = {"dataType": "Banner","id": 1299,"title": "训练营"}
if (responseCallback) {
responseCallback(JSON.stringify(jsonObject));
}
})
六、源码分析
PieBridge的本质是Native调用Js是通过loadUrl的方式执行调用Js中的方法;然后Js调用Native的本质是通过拦截两个URL(队列中有消息以及取队列中消息行为),来自Js层的调用Message消息信息,通过URL带过来。拦截URL取出消息执行相应的Native层的方法。
1、初始化
首先,在Native和Js层会初始化一个方法名(handlerName)和方法对象(handler)的哈希映射表。Native调用Js会传入唯一映射对应的方法名去Js层中找到相应方法对象,并执行这个方法。同理,Js调用Native也是通过唯一映射的方法名去Native层找到相应的Native的Handler并执行。
2、Native 调用 Js的原理(带Callback)
步骤1: Native调用callHandler, 传入所需参数
Native层通过调用
callHandler
方法(传入hanlderName
参数也即是Js层注册方法名,传入data
参数也即是Js层方法所需参数,传入callback
参数,用于回调Js层回调所需callback函数)。mPieBridge.callHandler("functionInJs", json, object : IHandlerCallback { override fun onCallback(data: String) { val jsonObject = JSONObject(data) Toast.makeText(this@MainActivity, "来自Js的数据: ${jsonObject.getStringValue("id")}", Toast.LENGTH_SHORT) .show() } }) //执行JS中注册的方法 override fun callHandler(id: String?, data: String, callback: IHandlerCallback?) { mMessageManager.postMessage(data, id, callback) }
步骤2: 调用MessageManager中的postMessage方法, 传入所需参数;构建Message实体,存储Callback
MessageManger
中的postMessage
首先会通过buildMessage
方法,根据传入参数构建一个Message
实体。如果callback
不等于null, 生成唯一映射的callbackId保存在Message
实体中, 并根据callbackId以及callback实例,保存在一个callback哈希映射表中。最后通过enqueueMessage
方法将消息插入MessageQueue
或立即分发dispatchMessage
。//构建消息实体Message private fun buildMessage( data: String, handlerId: String?, callback: IHandlerCallback? ): Message { val message = Message(Uri.encode(data, "UTF-8"))//注意: data传递JSON需要encode if (handlerId != null && handlerId.isNotBlank()) { message.handlerName = handlerId } if (callback != null) { //生成唯一callbackID val callbackID = PieBridgeHelper.processCallbackID(++mUniqueCallbackID) //把callbackID和callback保存在mCallbackMap中 mCallbackMap[callbackID] = callback //并把callbackID保存在Message实体中 message.callbackId = callbackID } return message } override fun postMessage(data: String, handlerId: String?, callback: IHandlerCallback?) { //通过enqueueMessage将消息Message插入队列或分发消息 enqueueMessage(buildMessage(data, handlerId, callback)) }
步骤3: 调用MessageManager中的enqueueMessage方法, 插入MessageQueue或立即dispatchMessage.
如果WebView此时已经加载完毕了,就调用
dispatchMessage
方法立即分发Message, 如果WebView还未加载完毕,那么就把Message
插入到MessageQueue
中,等待WebView加载完毕后,通过flushMessageQueue
方法,把队列中Message按照顺序通过dispatchMessage
方法全部分发出去。private fun enqueueMessage(message: Message) { val pageIsFinished = mListener.mPageIsFinishedAction?.invoke() ?: false //页面还未加载完毕,暂时先把消息加入startupMessage,带页面加载完毕批量分发消息 if (!pageIsFinished) {//未加载完毕,Message入消息队列 mMessageQueue.offer(message) } else {//如果是页面是已经加载完毕,可以立即分发消息 dispatchMessage(message) } }
步骤4: 调用MessageManager中的dispatchMessage方法, 把Message序列化成json,并拼接成jsCommand, 映射调用js中的_handleMessageFromNative。最后通过loadUrl执行这个jsCommand
private fun dispatchMessage(message: Message) { if (Thread.currentThread().isMainThread()) { val jsCommand = PieBridgeHelper.processJsCommand(message) Log.d("MessageManager", jsCommand) //jsCommand输出为: javascript:PieBridge._handleMessageFromNative('{data:xxx, handler_name:xxx, callback_id:xxx, response_id:null, response_data:null}'); mListener.mCallJsFuncAction?.invoke(jsCommand)//webview.loadUrl(jsCommand) } }
步骤5: 执行PieBridge.js层的中_handleMessageFromNative方法
首先,需要判断当前Js中的PieBridge对象是否初始化完毕,未初始化完毕就将
messageJSON
插入到Js中的ReceiveMessageQueue
中,等待PieBridge初始化完毕,通过init
方法中,将ReceiveMessageQueue
中所有MessageJSON
通过_dispatchMessageFromNative
方法分发出去;如果ReceiveMessageQueue
是空,说明此时PieBridge已经初始化完毕,可以直接调用_dispatchMessageFromNative
分发当前消息messageJSON
.// 提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以 function _handleMessageFromNative(messageJSON) { if (receiveMessageQueue) {//receiveMessageQueue不为null,说明PieBridge对象还没初始化完毕 receiveMessageQueue.push(messageJSON); } _dispatchMessageFromNative(messageJSON); }
步骤6: 执行PieBridge.js层的中_dispatchMessageFromNative方法
在该方法中首先拿到
messageJSON
, 并对它做解析将它转化成js层的Message
,先判断responseId
是否为null,由于第一次是Native调用Js, 而不是js调用native。所以为null. 如果responseId为null, 然后再判断callbackId
是否为null, 如果callbackId
不为null,那么就创建一个以responseData
为回调参数的responseCallback
, 最后取出Message
中的handlerName
去js层中的方法哈希映射表中,找到对应js层方法,并把Message
中的data
以及responseCallback
作为方法参数,执行对应js方法。// 提供给native使用 function _dispatchMessageFromNative(messageJSON) { setTimeout(function() { const message = JSON.parse(messageJSON); message.data = decodeURIComponent(message.data) let responseCallback; if (message.responseId) { responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else { // 直接发送 if (message.callbackId) { const callbackResponseId = message.callbackId; //注意:如果js层方法执行完毕后,有回调数据到Native层会触发responseCallback responseCallback = function(responseData) { //回调到Native层实际上就是把Native层传入的callbackId和回调的数据组成一个Message,又将消息发送到Native层 _doSend({ responseId: callbackResponseId, responseData }); }; } let handler = PieBridge._messageHandler; //找到js中的方法映射表中的对应handlerName的方法 if (message.handlerName) { handler = messageHandlers[message.handlerName]; } // 查找指定handler try { //传入data以及刚创建的responseCallback为参数执行对应的目标方法。 handler(message.data, responseCallback); } catch (exception) { if (typeof console !== 'undefined') { console.log( 'PieBridge: WARNING: javascript handler threw.', message, exception, ); } } } }); }
步骤7: 执行main.js层的中注册的目标方法(到这里native就成功地执行了js中的方法)
//main.js window.PieBridge.registerHandler('functionInJs', function(data, responseCallback) { document.querySelector('body').innerHTML = `data from Java: = ${data}`; var jsonObject = {"dataType": "Banner","id": 1299,"title": "训练营"} if (responseCallback) {//通过responseCallback把js带给native的数据回调出去,可以是函数直接返回同步数据,也可以是异步数据。 responseCallback(JSON.stringify(jsonObject)); } });
步骤8: 执行传入的responseCallback(处理js回调到native的callback)
执行
responseCallback
,实际上将native传入的callbackId
作为responseId
以及responseData
组装一个Message
, 通过_doSend
方法将消息传递到Native层。// 直接发送 if (message.callbackId) { const callbackResponseId = message.callbackId; responseCallback = function(responseData) { //执行_doSend方法,传入Message,但是callback为null, 仅仅响应native层的回调。 _doSend({ responseId: callbackResponseId, responseData }); }; }
步骤9: 执行PieBridge.js中的_doSend方法
类似native层的
enqueueMessage
方法,如果此时需要native的回调,在js会生成一个唯一的callbackId
,并把这个callbackId
和reponseCallback
,存入到js中的哈希映射表中。并把callbackId
存入到Message中,但是这里是处理js回调到native层,所以reponseCallback
为null, 那么就是直接把message
push到js层的队列,最后发出第一个URL(piebridge://_queueHasMessage/)拦截,告诉Native层此时js层队列中有待处理的消息,提醒Native来获取消息。// sendMessage add message, 触发native处理 sendMessage function _doSend(message, responseCallback) { if (responseCallback) { const callbackId = `cb_${uniqueId++}_${new Date().getTime()}`; responseCallbacks[callbackId] = responseCallback; message.callbackId = callbackId; } //往发送消息队列sendMessageQueue中push消息 sendMessageQueue.push(message); //并通知native,队列中有消息了,发出URL拦截:piebridge://_queueHasMessage/ messagingIframe.src = `${CUSTOM_PROTOCOL_SCHEME}://${QUEUE_HAS_MESSAGE}`; }
步骤10: Native层通过webview中的shouldOverrideUrlLoading中拦截URL为:piebridge://_queueHasMessage/;触发MessageManager中的interceptMessage
在
MessageManager
中的interceptMessage
方法拦截指定URL的path,比如_queueHasMessage。然后执行flushJsMessageQueue
方法。
mBridgeWebView.setListener {
onUrlLoading { url ->
return@onUrlLoading mMessageManager.interceptMessage(url)//执行MessageManager中的interceptMessage
}
onPageFinished {
if (!mLoadFinished) {//页面加载完毕,将队列中所有消息全部分发出去
mMessageManager.flushMessageQueue()
mLoadFinished = true
}
}
}
//拦截JS中的消息
override fun interceptMessage(url: String): Boolean {
val decodeUrl = URLDecoder.decode(url, "UTF-8")
//拦截带返回值的URL
if (decodeUrl.startsWith(PieBridgeHelper.URL_RETURN_DATA)) {
handleMessageReturnData(decodeUrl)
return true
//拦截队列中有消息的URL
} else if (decodeUrl.startsWith(PieBridgeHelper.URL_HAS_MESSAGE)) {
flushJsMessageQueue()//执行flushJsMessageQueue
return true
}
return false
}
步骤11: Native层执行flushJsMessageQueue方法,通过组装一个jsCommand,通过loadUrl执行js中的_fetchQueue方法, 并注入一个callback用于回调获取_fetchQueue中的队列中数据。
调用callJsFuncCallback方法,执行jsCommand并把获取队列数据callback,以_fetchQueue方法名为key,callback为value存入Native层的callback哈希表中。
private fun flushJsMessageQueue() {
callJsFuncCallback(PieBridgeHelper.JS_CMD_FETCH_QUEUE, object :
IHandlerCallback {
override fun onCallback(data: String) {
handleMessageQueue(data)
}
})
}
private fun callJsFuncCallback(jsCommand: String, callback: IHandlerCallback) {
if (Thread.currentThread().isMainThread()) {
//最后通过loadUrl执行jsCommand: javascript:PieBridge._fetchQueue();
mListener.mCallJsFuncAction?.invoke(jsCommand)
}
//以_fetchQueue方法名为key,callback为value存入Native层的callback哈希表中
mCallbackMap[PieBridgeHelper.processFuncName(jsCommand)] = callback
}
步骤12: Js层会执行_fetchQueue方法
执行js中的
_fetchQueue
方法,会把当前js中的sendMessageQueue
待发送消息队列中所有的Message序列化成json,并清空js中的sendMessageQueue
待发送消息队列。最后把所有消息json作为path,拼接到返回数据URL的尾部: piebridge://return/_fetchQueue/[{data:xxx, callbackId:xxx},…]
// 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容
function _fetchQueue() {
const messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
// android can't read directly the return data, so we can reload iframe src to communicate with java
if (messageQueueString !== '[]') {
bizMessagingIframe.src = `${CUSTOM_PROTOCOL_SCHEME}://return/_fetchQueue/${encodeURIComponent(
messageQueueString,
)}`;
}
}
步骤13: Native层会拦截js的URL: piebridge://return/_fetchQueue/[{data:xxx, callbackId:xxx},…]
Native层会拦截携带js层Message信息数据的url, 然后会调用native层的
handleMessageReturnData
//拦截JS中的消息
override fun interceptMessage(url: String): Boolean {
val decodeUrl = URLDecoder.decode(url, "UTF-8")
//拦截带返回值的URL
if (decodeUrl.startsWith(PieBridgeHelper.URL_RETURN_DATA)) {
handleMessageReturnData(decodeUrl)//用native层的handleMessageReturnData
return true
//拦截队列中有消息的URL
} else if (decodeUrl.startsWith(PieBridgeHelper.URL_HAS_MESSAGE)) {
flushJsMessageQueue()
return true
}
return false
}
步骤14: 执行handleMessageReturnData方法,从url中取方法名_fetchQueue以及js队列中的数据
url中取出的方法名
_fetchQueue
实际上是为了找到步骤11中,以_fetchQueue
存入到Native层callback映射表中对应的callback, 这个callback就是为了把队列中的数据回调出去。
private fun handleMessageReturnData(decodeUrl: String) {
//从url中取出方法名_fetchQueue
val funcName = PieBridgeHelper.processFuncFromUrl(decodeUrl)
//从url中取出js层队列数据data
val data = PieBridgeHelper.processDataFromUrl(decodeUrl)
//然后从native中的callback表中,找到方法名_fetchQueue为callback
val callback = mCallbackMap[funcName]
if (callback != null) {
//执行callback,把队列消息集合通过onCallback回调出去, 实际上就是回调到了callJsFuncCallback中的onCallback方法
callback.onCallback(data)
//最后移除方法名_fetchQueue的callback
mCallbackMap.remove(funcName)
return
}
}
步骤15: 执行mCallbackMap中找到的callback, 实际上就是执行了flushJsMessageQueue中的执行的callJsFuncCallback中的onCallback回调
private fun flushJsMessageQueue() {
callJsFuncCallback(PieBridgeHelper.JS_CMD_FETCH_QUEUE, object :
IHandlerCallback {
override fun onCallback(data: String) {
//会触发onCallback的回调,然后执行handleMesssageQueue方法。
handleMessageQueue(data)
}
})
}
步骤16: 最后执行handleMessageQueue方法
执行handleMessageQueue方法,把js层队列中的data转化成一个MessageList,然后遍历这个list去读每一个Message,如果Message中的responseId不为空,说明这是来自js层的回调消息,那么就会拿着这个responseId去native中的mCallbackMap找到对应的callback, responseData就是js层返回的数据,利用这个callback回调responseData即可。
private fun handleMessageQueue(data: String) {
val messageList: List<Message>
try {
messageList = convertMessageList(data)
} catch (e: Exception) {
e.printStackTrace()
return
}
if (messageList.isEmpty()) {
return
}
for (message in messageList) {
//如果responseId不为空,说明这是来自js层的回调消息,那么就会拿着这个responseId去native中的mCallbackMap找到对应的callback, responseData就是js层返回的数据,利用这个callback回调responseData即可
if (!message.responseId.isNullOrEmpty()) {
val resId = requireNotNull(message.responseId)
val callback = mCallbackMap[resId]
//执行callback回调触发onCallback方法,回调message.responseData
callback?.onCallback(message.responseData ?: "")
//最后移除在mCallbackMap中对应resId的callback
mCallbackMap.remove(resId)
continue
}
val callback: IHandlerCallback
val callbackID = message.callbackId
if (!callbackID.isNullOrEmpty()) {
callback = object : IHandlerCallback {
override fun onCallback(data: String) {
val tempMsg = Message(data).apply {
responseId = callbackID
responseData = data
}
enqueueMessage(tempMsg)
}
}
} else {
callback = object : IHandlerCallback {
override fun onCallback(data: String) {
//do nothing
}
}
}
val handler: IMessageHandler? = if (!message.handlerName.isNullOrEmpty()) {
mListener.mFindHandlerAction?.invoke(requireNotNull(message.handlerName))
} else {
DefaultMessageHandler
}
handler?.handle(message.data, callback)
}
}
步骤17: 最后执行calback方法,并获取到js层的数据
mPieBridge.callHandler("functionInJs", json, object : IHandlerCallback {
override fun onCallback(data: String) {
val jsonObject = JSONObject(data)
Toast.makeText(this@MainActivity, "来自Js的数据: ${jsonObject.getStringValue("id")}", Toast.LENGTH_SHORT)
.show()
}
})
- 3、Js 调用 Native的原理(带Callback): 同理分析