JsBridge源码解析

本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发。

转载请注明出处

引言

app的快速迭代,离不开h5的支持。而如何解决Js和Native的通信问题,就需要JsBridge来解决了。本文主要是对第三方库JsBridge的源码解析。

项目地址

Bridge基本原理

  • Js通知Native

    Js通知Native目前有三种方案。

    • API注入。通过webview.addJavascriptInterface()的方法实现。
    • 拦截Js的alert/confirm/prompt/console等事件。由于prompt事件在js中很少使用,所以一般是拦截该事件。这些事件在WebChromeClient都有对应的方法回调(onConsoleMessage,onJsPrompt,onJsAlert,onJsConfirm)
    • url跳转拦截,对应WebViewClient的shouldOverrideUrlLoading()方法。

    第一种方法,由于webview在4.4以下的安全问题,所以有版本兼容问题。后两种方法原理本质上是一样的,都是通过对webview信息冒泡传递的拦截,通过定制协议-拦截协议-解析方法名参数-执行方法-回调。

  • Native通知Js

    webview可以通过loadUrl()的方法直接调用。在4.4以上还可以通过evaluateJavascript()方法获取js方法的返回值。

    4.4以前,如果想获取方法的返回值,就需要通过上面的对webview信息冒泡传递拦截的方式来实现。

JsBridge源码解析

我们项目是采用url跳转拦截的方式实现Native与Js的通信。

  • JsBridge的接入

    JsBridge的接入分为两部分,H5端的接入,客户端的接入。

    • 客户端的接入。

      BridgeWebView这个类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      //js注入的文件名	
      String toLoadJs = "WebViewJavascriptBridge.js";
      //白名单的实现接口 由外部自己实现
      private IBridgeAccept mAccept;

      public void onPageFinished(WebView view, String url) {
      //String toLoadJs = "WebViewJavascriptBridge.js";
      if (mAccept != null && mAccept.accept(url) && toLoadJs != null ) {
      BridgeUtil.webViewLoadLocalJs(view, toLoadJs);
      }
      }

      BridgeUtil的webViewLoadLocalJs方法

      1
      2
      3
      4
      5
      6
      7
      public static void webViewLoadLocalJs(WebView view, String path) {
      //如果为空,则去assets目录下读取js文件
      if (null == jsContent) {
      jsContent = assetFile2Str(view.getContext(), path);
      }
      view.loadUrl("javascript:" + jsContent);
      }

      从上面的代码可以看出,JsBridge实现的JS部分代码也是放在了客户端的assets目录下。在页面加载完成后,由客户端主动注入。并且这里存在域名白名单机制,只有在白名单内才会注入js代码。

    • H5端的接入

      由于客户端注入js是异步的,H5调用方法时,必须要确保js代码注入成功。因此必须要监听注入事件,成功后通知H5。
      注入的

      WebViewJavascriptBridge.js部分代码

      1
      2
      3
      4
      5
      6
      7
      8
      //自定义jsBridge初始化事件WebViewJavascriptBridgeReady,并在最后主动触发事件
      //通知h5jsBridge初始化完毕
      var doc = document;,
      _createQueueReadyIframe(doc);
      var readyEvent = doc.createEvent('Events');
      readyEvent.initEvent('WebViewJavascriptBridgeReady');
      readyEvent.bridge = WebViewJavascriptBridge;
      doc.dispatchEvent(readyEvent);

      在h5调用Bridge方法时,需要监听Bridge初始化事件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      //已经初始化了
      if (window.WebViewJavascriptBridge) {
      //do your work here
      } else {
      //监听初始化事件
      document.addEventListener(
      'WebViewJavascriptBridgeReady', function() {
      //do your work here
      }, false);
      }
  • Native与Js通信的流程

    • Native调用Js

      先看一张我画的流程图

      举个例子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      webview.callHandler("functionInJs", "哈哈我是java传来的", new CallBackFunction() {

      @Override
      public void onCallBack(String data) {
      Log.e("MainActivity", "reponse data from js " + data);
      }

      @Override
      public void onFailed(String data) {
      super.onFailed(data);
      Log.e("MainActivity", "onFailed data from js " + data);
      }
      });

      看下这个方法做了什么

      1
      2
      3
      4
      5
      6
      7
      //java调用Js
      //handlerName是Js提前注册的方法名 data是方法的参数
      //callBack 是java的回调对象
      public void callHandler(String handlerName, String data,
      CallBackFunction callBack) {
      doSend(handlerName, data, callBack);
      }

      这里是流程的入口,native调用js提前预注册的方法,这里注意方法名一定要和JS预定义的方法名完全相同,才会调用成功。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      private void doSend(String handlerName, String data,
      CallBackFunction responseCallback) {
      //将要调用的js方法名和参数封装成Message
      Message m = new Message();
      if (!TextUtils.isEmpty(data)) {
      m.setData(data);
      }
      //如果java需要回调,生成唯一的回调id,放到message中
      //并且将对应的java回调保存到 responseCallbacks中,以callbackId为键
      if (responseCallback != null) {
      String callbackStr = String.format(
      BridgeUtil.CALLBACK_ID_FORMAT,
      ++uniqueId
      + (BridgeUtil.UNDERLINE_STR + SystemClock
      .currentThreadTimeMillis()));
      responseCallbacks.put(callbackStr, responseCallback);
      m.setCallbackId(callbackStr);
      }

      if (!TextUtils.isEmpty(handlerName)) {
      m.setHandlerName(handlerName);
      }

      queueMessage(m);
      }

      在doSend函数中,主要做的就是将方法名、参数、回调id封装message。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      private void queueMessage(Message m) {
      //startupMessage在页面第一次加载完成就会置空,所以这里一定会走到dispatchMessage中
      if (startupMessage != null) {
      startupMessage.add(m);
      } else {
      dispatchMessage(m);
      }
      }

      private void dispatchMessage(Message m) {
      //message转成json,并进行转义
      String messageJson = m.toJson();
      messageJson = messageJson.replaceAll("\\\\/", "/");
      messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");
      messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");
      //将转义好的消息json串传递到js的_handleMessageFromNative方法中
      //并在主线程中调用
      String javascriptCommand = String.format(
      BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
      messageJson = null;
      if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
      WYLogUtils.i(TAG, "dispatchMessage --> " + javascriptCommand);
      this.loadUrl(javascriptCommand);
      }
      }

      这里的BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA是js的方法名,native可以通过loadUrl()的方式调用

      1
      2
      final static String JS_HANDLE_MESSAGE_FROM_JAVA  
      = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');";

      到这里就把要调用的方法名,参数,是否需要回调的消息就传给了js

      再看下js的_handleMessageFromNative做了什么

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      function _handleMessageFromNative(messageJSON) {
      //receiveMessageQueue在页面加载完成后就赋值为null了
      //所以最后都会到_dispatchMessageFromNative中
      if (receiveMessageQueue) {
      receiveMessageQueue.push(messageJSON);
      } else {
      _dispatchMessageFromNative(messageJSON);
      }
      }

      function _dispatchMessageFromNative(messageJSON) {
      setTimeout(function() {
      //将传递的消息json串转为message对象
      var message = JSON.parse(messageJSON);
      var responseCallback;
      //java调用js 并没有responseId 所以走else

      //这里是js调用java并且js有回调,这里表明java将js回调需要的数据传过来了
      //此时再进行js 回调处理
      if (message.responseId) {
      responseCallback = responseCallbacks[message.responseId];
      if (!responseCallback) {
      return;
      }
      responseCallback(message.responseData);
      delete responseCallbacks[message.responseId];
      } else {
      //直接发送
      ////这里如果有回调id, 说明前面java需要js回传数据
      //构造回调对象
      if (message.callbackId) {
      var callbackResponseId = message.callbackId;
      responseCallback = function(responseData) {
      _doSend({
      responseId: callbackResponseId,
      responseData: responseData
      });
      };
      }
      //从js预定义的方法集合messageHandlers中,匹配传递过来消息中方法名
      var handler = WebViewJavascriptBridge._messageHandler;
      if (message.handlerName) {
      handler = messageHandlers[message.handlerName];
      }
      //匹配成功后,调用该方法,传递数据,并且将回调对象作为参数也传递了
      try {
      handler(message.data, responseCallback);
      } catch (exception) {
      if(responseCallback){
      responseCallback({error:404,errorMessage:"JS API not find"})
      }
      if (typeof console != 'undefined') {
      console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
      }
      }
      }
      });
      }

      这里的messageHandlers是从哪来的?看下面的代码

      1
      2
      3
      4
      5
      6
      7
      //定义方法集对象
      var messageHandlers = {};

      //js注册方法时,其实就是以注册的方法名为key, 具体的方法对象为值存储到messageHandler中
      function registerHandler(handlerName, handler) {
      messageHandlers[handlerName] = handler;
      }

      到这里,native已经成功调用js的方法了。但是如果java还需要js回传数据的话,那么在js注册的方法中,需要主动使用上面传递进参数的回调对象调用其方法才可以完成。
      如这样。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      //js 注册了一个名为functionInJs的方法 这里的responseCallback就是传递的回调对象
      bridge.registerHandler("functionInJs", function(data, responseCallback) {
      document.getElementById("show").innerHTML = ("data from Java: = " + data);
      /因为java不一定需要js回传数据,只有需要的时候,才会传入该对象,所以这里判空
      if (responseCallback) {
      var responseData = {data:'Javascript Says Right back aka!'};
      //使用该回调对象主动调用其方法
      responseCallback(responseData);
      }
      });

      主动调用该方法后,我看回看上面的代码。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      if (message.callbackId) {
      var callbackResponseId = message.callbackId;
      //其实就是调用该方法
      responseCallback = function(responseData) {
      _doSend({
      responseId: callbackResponseId,
      responseData: responseData
      });
      };
      }

      上面分析过,这里是如果java需要js回传数据,也就是message中有callbackId,会创建回调对象。而这时h5端主动调用该回调的对象的方法,其实就是走到了这里。会把回传的数据和该callbackId封装成message对象,而callbackId这时就成了responseId。这里其实也是为了在java端能够根据id取出最初存入的回调。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      function _doSend(message, responseCallback) {
      //这时的responseCallback为null
      if (responseCallback) {
      var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
      responseCallbacks[callbackId] = responseCallback;
      message.callbackId = callbackId;
      }
      //将上步的message放入sendMessageQueue发送消息队列中
      sendMessageQueue.push(message);
      //改变iframe的src从而通知native从h5取消息
      messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
      }

      这里的messagingIframe是什么呢?看注入的js代码。

      1
      2
      3
      4
      5
      6
      7
      8
      //声明iframe元素
      var messagingIframe;
      //创建不可见的的ifrmae
      function _createQueueReadyIframe(doc) {
      messagingIframe = doc.createElement('iframe');
      messagingIframe.style.display = 'none';
      doc.documentElement.appendChild(messagingIframe);
      }

      通过改变iframe的src会触发WebViewClient的shouldOverrideUrlLoading()。这里可以看到上面将iframe的src改为yy://__QUEUE_MESSAGE__/,通知Native去取消息。

      1
      2
      //通知Native有消息的协议
      yy://__QUEUE_MESSAGE__/

      下面看看Native做了什么

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      @Override
      public boolean shouldOverrideUrlLoading(WebView view, String url) {
      WYLogUtils.i(TAG, "shouldOverrideUrlLoading --> " + url);
      //yy://return/{function}/returncontent || yy://"
      //这里开始拦截协定的协议
      if (url.startsWith(BridgeUtil.YY_RETURN_DATA) || url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) {
      //解码
      try {
      url = URLDecoder.decode(url, "UTF-8");
      } catch (IllegalArgumentException e) { //解决未编码同时内容中有%时奔溃的问题
      WYLogUtils.e(TAG, e.getMessage(), e);
      if (url.contains("%%")) {
      try {
      url = URLDecoder.decode(removeDoublePercent(url), "UTF-8");
      } catch (Exception e1) {
      WYLogUtils.e(TAG, e1.getMessage(), e1);
      }
      }
      } catch (Exception e) {
      WYLogUtils.e(TAG, e.getMessage(), e);
      }
      //yy://return/{function}/returncontent || yy://
      //这里由于上面是为yy://__QUEUE_MESSAGE__/ 所以会走到flushMessageQueue()中
      if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {// 如果是返回数据
      handlerReturnData(url);
      } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) {
      flushMessageQueue();
      }
      return true;
      }
      ...
      }

      这里拦截到预定的协议后,调用了flushMessageQueue()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      private void flushMessageQueue() {
      //这里在主线程
      if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
      //这里往loadUrl方法中传了一个字符串和一个新的回调对象
      loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA,new CallBackFunction() {
      @Override
      public void onCallBack(String data) {
      ...
      }
      }

      再看下BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA是什么

      1
      2
      //这是个js方法 
      final static String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();";

      再看下这里的loadUrl(String jsUrl, CallBackFunction returnCallback)方法做了什么

      1
      2
      3
      4
      5
      6
      private void loadUrl(String jsUrl, CallBackFunction returnCallback) {
      //主线程执行了js的_fetchQueue方法
      this.loadUrl(jsUrl);
      responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl),
      returnCallback);
      }

      看下BridgeUtil.parseFunctionName(jsUrl)是什么

      1
      2
      3
      4
      //从字符串中解析出js的方法名
      public static String parseFunctionName(String jsUrl) {
      return jsUrl.replace("javascript:WebViewJavascriptBridge.", "").replaceAll("\\(.*\\);", "");
      }

      所以在loadUrl方法中做了两件事。第一执行_fetchQueue方法,第二以_fetchQueue为key,将上面创建的回调对象为值,存到了responseCallbacks中。

      下面看看_fetchQueue方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      function _fetchQueue() {
      //将上面要回传给native的消息sendMessageQueue转为json
      //所有的消息
      var messageQueueString = JSON.stringify(sendMessageQueue);
      sendMessageQueue = [];
      //这里肯定是Android Ios多余 用的不是一个框架
      if(isAndroid()){
      //通知Native过来拿消息。
      messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + messageQueueString;
      }else if (isIphone()) {
      return messageQueueString;
      //android can't read directly the return data, so we can reload iframe src to communicate with java
      }
      }

      这里将要回传给native的消息转为json,放到iframe的src中,从而通知native取消息。

      1
      2
      //返回数据的协议
      yy:///return/_fetchQueue/+json

      触发WebViewClient的shouldOverrideUrlLoading(),拦截该url,并调用handlerReturnData(url)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      final static String YY_OVERRIDE_SCHEMA = "yy://";	
      final static String YY_RETURN_DATA = YY_OVERRIDE_SCHEMA + "return/";/

      //shouldOverrideUrlLoading方法中的代码片段
      if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {// 如果是返回数据
      handlerReturnData(url);
      } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) {
      flushMessageQueue();
      }

      handlerReturnData方法中处理回传的数据,并执行前面存入回调对象的方法。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      private void handlerReturnData(String url) {
      //functionName: _fetchQueue
      //以_fetchQueue为key从responseCallbacks中取出上面存入的回调对象
      String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
      CallBackFunction f = responseCallbacks.get(functionName);
      //从回传数据中解析出回传的数据
      String data = BridgeUtil.getDataFromReturnUrl(url);
      if (f != null) {
      //执行回调对象的方法,并传入数据
      f.onCallBack(data);
      //移除回调
      responseCallbacks.remove(functionName);
      }
      }

      回调对象的方法看看做了什么

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA,
      new CallBackFunction() {

      @Override
      public void onCallBack(String data) {
      List<Message> list = null;
      try {
      //将回传的data转成Message集合
      list = Message.toArrayList(data);
      } catch (Exception e) {
      WYLogUtils.e(TAG, e.getMessage(), e);
      }
      if (list == null || list.size() == 0) {
      return;
      }
      //由于在js _fetchQueue方法中是将整个消息队列发送过来 遍历集合,这里面有我们分析的那条消息
      for (int i = 0; i < list.size(); i++) {
      Message m = list.get(i);
      //分析的那条消息带有responseId
      String responseId = m.getResponseId();
      //说明java调用了js 且需要js回传数据
      if (!TextUtils.isEmpty(responseId)) {
      //而这个responseId其实就是一开始java需要回调时生成的callbackId
      //根据这个callbackId从responseCallbacks取出最开始存入的回调对象 并且执行该回调方法
      CallBackFunction function = responseCallbacks
      .get(responseId);
      String responseData = m.getResponseData();
      function.onInnerCallBack(responseData);
      //执行后移除该回调对象
      responseCallbacks.remove(responseId);
      }else{
      .....
      }
      }
      }
      }
      );

      再看下回调对象这个类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      public abstract class CallBackFunction {
      //执行回调的方法
      public void onInnerCallBack(String data){
      //将js回传的消息转成json,并解析error属性,默认是200表明调用js方法成功
      //如果解析出404表明调用js方法失败,
      if(data != null && data.length()>0){
      try {
      JSONObject obj = new JSONObject(data);
      int resultCode = obj.optInt("error",200);
      if(resultCode == 404){
      onFailed(data);
      }else{
      onCallBack(data);
      }
      } catch (JSONException e) {
      e.printStackTrace();
      }
      }else{
      onCallBack(data);
      }

      }

      //抽象方法,native注册方法时的回调对象的回调方法
      public abstract void onCallBack(String data);

      public void onFailed(String data){

      }
      }

      到这里Native调用js分析完毕。从整个流程来看,Native消息通知JS很方便,直接调用就好了。而Js由于不能直接通知给Native,所以相对来说比较绕,得先通过协议拦截的方式通知Native JS有消息要传递,然后Native再主动调用Js的方法,将Js要传递的消息再通过协议拦截的方式传递给Native。

  • Js调用Native

    还是看张我画的流程图。

    js调用Native的流程其实和Native调用Js基本是相似的,所以这里就简单介绍了。

    依然是举个例子

    1
    2
    3
    4
    5
    6
    7
    window.WebViewJavascriptBridge.callHandler(
    'callNative'
    , {'param': '哈哈哈哈'}
    , function(responseData) {
    document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
    }
    );

    js调用callHandler方法,将要调用的方法名和参数以及回调对象传入doSend()方法中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function _doSend(message, responseCallback) {
    //js如果需要回调 生成callbackId与方法名,参数封装成message
    if (responseCallback) {
    var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
    //将Callback保存到responseCallbacks 以callbackId为key
    responseCallbacks[callbackId] = responseCallback;
    message.callbackId = callbackId;
    }
    //将message放到消息队列中
    sendMessageQueue.push(message);
    //通知Native过来取消息
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

    native拦截该协议,会调用flushMessageQueue,这里和上面Java调用js一样。webview通过loadUrl的方式,调用js的_fetchQueue方法。同时生成回调对象,保存到responseCallbacks中。

    f_fetchQueue方法上面也分析过了,会将js的消息队列转为json放到iframe的src中,然后再次通知Native过来取消息。

    Native拦截收到消息后,调用handlerReturnData()方法。从responseCallbacks取出上面注册的回调对象,调用其方法,并移除该回调对象。

    下面看回调方法里面做了什么。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA,
    new CallBackFunction() {

    @Override
    public void onCallBack(String data) {
    //这里和前文一样,取出js传过来的消息
    //将消息转为message集合 遍历集合
    ....
    for (int i = 0; i < list.size(); i++) {
    Message m = list.get(i);
    //这里和前文一样 由于这次js传过来的消息里并没有responseId 所以直接看else
    String responseId = m.getResponseId();
    if (!TextUtils.isEmpty(responseId)) {
    ...
    }else{
    //js 调用了 java
    //如果有callbackId,说明js要回传数据
    //创建回调函数
    CallBackFunction responseFunction = null;
    final String callbackId = m.getCallbackId();
    if (!TextUtils.isEmpty(callbackId)) {
    responseFunction = new CallBackFunction() {

    @Override
    public void onCallBack(String data) {
    Message responseMsg = new Message();
    responseMsg.setResponseId(callbackId);
    responseMsg.setResponseData(data);
    queueMessage(responseMsg);
    }
    }else{
    //不需要就构建默认的
    responseFunction = new CallBackFunction() {
    @Override
    public void onCallBack(String data) {
    //no-op
    }
    }
    }
    //从消息中取出方法名去匹配native提前的注册的方法池
    //如果存在就取出方法对象并回调该方法。
    BridgeHandler handler;
    if (!TextUtils.isEmpty(m.getHandlerName())) {
    handler = messageHandlers.get(m.getHandlerName());
    if (handler == null) {
    handler = defaultHandler;
    }
    } else {
    handler = defaultHandler;
    }
    handler.handler(specialCharacterReplace(m.getData()), responseFunction);
    }
    }
    });

    下面再分析java数据如何回传的。上面已经分析了,如果js需要Java回传数据,是会创建一个回调对象,并传入的要调用的Handler的方法中。java端就可以使用该对象调用其方法来进行回传数据。

    看看该方法做了什么。

    1
    2
    3
    4
    5
    6
    //将要回传的数据封装成message,原先从js传过来的callbackId变成responseId
    Message responseMsg = new Message();
    //设置responseId
    responseMsg.setResponseId(callbackId);
    responseMsg.setResponseData(data);
    queueMessage(responseMsg);

    继而调用queueMessage()方法,最终会调到dispatchMessage。这里就和前文分析的一样,会将传过来的message转成json,再进行转义,随后在主线程调用JS的_handleMessageFromNative方法,并将json传递过去。
    _handleMessageFromNative()方法又会调用_dispatchMessageFromNative()方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function _dispatchMessageFromNative(messageJSON) {
    setTimeout(function() {
    var message = JSON.parse(messageJSON);
    var responseCallback;
    //java call finished, now need to call js callback function
    //js 调用 java 并且js有回调,这里表明 java将js回调需要的数据传过来了 此时再进行js 回调处理
    if (message.responseId) {
    responseCallback = responseCallbacks[message.responseId];
    if (!responseCallback) {
    return;
    }
    responseCallback(message.responseData);
    delete responseCallbacks[message.responseId];
    } else {
    ...
    }
    });
    }

    至此,Js调用Native完毕。

Bridge框架问题

分析源码后,可以看到Bridge目前存在以下问题

  • Js调用java偶现调用失败。因为通过iframe的机制并不能保证shouldOverrideUrlLoading每次都会调用。

  • 部分手机java调用js偶现失败。

    推测有两个原因:

    1. webview在调用js方法时,只在主线程调用。如果不在主线程的时候,就不会调用了
    2. Js注入的时机问题。在OnPageFinished中注入虽然最后都会全局注入成功,但是完成时间有可能太晚。