Android Native与Web JS双向通信框架-PieBridge

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,并把这个callbackIdreponseCallback,存入到js中的哈希映射表中。并把callbackId存入到Message中,但是这里是处理js回调到native层,所以reponseCallback为null, 那么就是直接把messagepush到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): 同理分析


   转载规则


《Android Native与Web JS双向通信框架-PieBridge》 mikyou 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Android布局、网络、电量、APP启动、APP瘦身分析和优化 Android布局、网络、电量、APP启动、APP瘦身分析和优化
Android布局、网络、电量、APP启动、APP瘦身分析和优化一、布局优化 布局优化的一个目的也就是减少层级的嵌套,提高渲染,从而避免卡顿问题出现。我们都知道大部分设备屏幕刷新率是60HZ,也就是渲染一帧的时间是16.6ms,如果出现某一
2019-12-22
下一篇 
文本引擎框架(TextEngine)渲染迁移至WebView文本渲染 文本引擎框架(TextEngine)渲染迁移至WebView文本渲染
由TextEngine原生渲染迁移至WebView渲染 一、背景现状应项目需要在原有渲染技术基础上(支持普通文本渲染、图片渲染、链接等,渲染元素过于单一)扩展支持音频、视频等丰富功能。Android因为基于自身开发的TextEngine(基
2019-12-21