Android侧webview与Js通信的方式(2)

Android侧webview与Js通信的方式(2)

上一篇讲到js与native通信方式的比较,但是只局限于api使用是否方便,是否存在系统漏洞等方面,并没有考虑js线程问题,场景较为单一,接下来我们从js线程角度看下这几个js-native api接口。

实验

JavaScript的函数调用栈与任务队列

总结

  1. JS 是单线程的,只有一个主线程
  2. 函数内的代码从上到下顺序执行,遇到被调用的函数先进入被调用函数执行,待完成后继续执行
  3. 遇到异步事件,浏览器另开一个线程,主线程继续执行,待结果返回后,执行回调函数

JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这样设计的方案主要源于其语言特性,因为 JavaScript 是浏览器脚本语言,它可以操纵 DOM ,可以渲染动画,可以与用户进行互动,如果是多线程的话,执行顺序无法预知,而且操作以哪个线程为准也是个难题。
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
在 HTML5 时代,浏览器为了充分发挥 CPU 性能优势,允许 JavaScript 创建多个线程,但是即使能额外创建线程,这些子线程仍然是受到主线程控制,而且不得操作 DOM,类似于开辟一个线程来运算复杂性任务,运算好了通知主线程运算完毕,结果给你,这类似于异步的处理方式,所以本质上并没有改变 JavaScript 单线程的本质。

函数调用栈与任务队列

  • 函数调用栈

    先看一段代码

function a(){  
  console.log("I'm a!");
};

function b(){  
  a();
  console.log("I'm b!");
};

b();

1、 第一步,执行这个文件,此文件会被压入调用栈(例如此文件名为 a.js)

call stack
a.js

2、第二步,遇到 b() 语法,调用 b() 方法,此时调用栈会压入此方法进行调用:

call stack
b()
a.js

3、第三步,调用 b() 函数时,内部调用的 a() ,此时 a() 将压入调用栈

call stack
a()
b()
a.js

4、第四步,a() 调用完毕输出 I'm a!,调用栈将 a() 弹出,就变成如下:

call stack
b()
a.js

5、第五步:b()调用完毕输出I'm b!,调用栈将 b() 弹出,变成如下:

call stack
a.js

6、第六步:main.js 这个文件执行完毕,调用栈将 b() 弹出,变成一个空栈,等待下一个任务执行:

call stack
  • 任务队列

    这就是一个简单的调用栈,在调用栈中,前一个函数在执行的时候,下面的函数全部需要等待前一个任务执行完毕,才能执行。

    但是,有很多任务需要很长时间才能完成,如果一直都在等待的话,调用栈的效率极其低下,所以这些任务主线程根本不需要等待,只要将这些任务挂起,先运算后面的任务,等到执行完毕了,再回头将此任务进行下去,于是就有了任务队列的概念。这些共同构成了javascript的事件循环机制。


    image

prompt与onJsPrompt

js侧

   function testpromt() {
       var result1=prompt("js://demo?arg1=111&arg2=222");
       console.log("result1:"+result1);
       var result2=prompt("js://demo?arg1=31&arg2=4");
       console.log("result2:"+result2);
       var result3=prompt("js://demo?arg1=5&arg2=6");
       console.log("result3:"+result3);
       var result4=prompt("js://demo?arg1=7&arg2=8");
       console.log("result4:"+result4);
       var result5=prompt("js://demo?arg1=9&arg2=10");
       console.log("result5:"+result5);
   }

native侧

                        @Override
                    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
                        // 根据协议的参数,判断是否是所需要的url(原理同方式2)
                        // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                        //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
                        Uri uri = Uri.parse(message);
                        // 如果url的协议 = 预先约定的 js 协议
                        // 就解析往下解析参数
                        if (uri.getScheme().equals("js")) {
                            // 执行JS所需要调用的逻辑
                            I++;
                            Log.e("chromium", "promt: " + "js调用了Android的方法" + i);
                            // 可以在协议上带有参数并传递到Android上
                            new Thread() {
                                @Override
                                public void run() {
                                        try {
                                            Thread.sleep(1000);
                                            result.confirm("js调用了Android的方法成功啦,间隔1s" + i);
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                    super.run();
                                }
                            }.start();
                            return true;
                        }
                        return super.onJsPrompt(view, url, message, defaultValue, result);
                    }

注意native侧的result.confirm(),该方法也可以直接返回数据,乍一看很方便,但是因为这种返回方式为同步返回,会阻塞js线程,如代码中逻辑,
var result2=prompt("js://demo?arg1=31&arg2=4")
这句必须等var result1=prompt("js://demo?arg1=111&arg2=222")这句执行js、native侧逻辑都执行完才会执行,及同步阻塞。看下log

    05-14 16:12:26.592 30985-30985/yjxiao.xbridge E/chromium: promt: js调用了Android的方法1
    05-14 16:12:27.597 30985-30985/yjxiao.xbridge I/chromium: [INFO:CONSOLE(186)] "result1:js调用了Android的方法成功啦,间隔11", source: file:///android_asset/demo.html (186)
    05-14 16:12:27.598 30985-30985/yjxiao.xbridge E/chromium: promt: js调用了Android的方法2
    05-14 16:12:28.604 30985-30985/yjxiao.xbridge I/chromium: [INFO:CONSOLE(188)] "result2:js调用了Android的方法成功啦,间隔12", source: file:///android_asset/demo.html (188)
    05-14 16:12:28.607 30985-30985/yjxiao.xbridge E/chromium: promt: js调用了Android的方法3
    05-14 16:12:29.611 30985-30985/yjxiao.xbridge I/chromium: [INFO:CONSOLE(190)] "result3:js调用了Android的方法成功啦,间隔13", source: file:///android_asset/demo.html (190)
    05-14 16:12:29.612 30985-30985/yjxiao.xbridge E/chromium: promt: js调用了Android的方法4
    05-14 16:12:30.617 30985-30985/yjxiao.xbridge I/chromium: [INFO:CONSOLE(192)] "result4:js调用了Android的方法成功啦,间隔14", source: file:///android_asset/demo.html (192)
    05-14 16:12:30.618 30985-30985/yjxiao.xbridge E/chromium: promt: js调用了Android的方法5
    05-14 16:12:31.623 30985-30985/yjxiao.xbridge I/chromium: [INFO:CONSOLE(194)] "result5:js调用了Android的方法成功啦,间隔15", source: file:///android_asset/demo.html (194)

JavascriptInterface

js侧

function testjvinterface() {
        test.hello("js调用了android中的hello方法1");
        console.log('js侧1');
        test.hello("js调用了android中的hello方法2");
        console.log('js侧2');
        test.hello("js调用了android中的hello方法3");
        console.log('js侧3');
        test.hello("js调用了android中的hello方法4");
        console.log('js侧4');
        test.hello("js调用了android中的hello方法5");
        console.log('js侧5');
    }

native 侧

// 定义JS需要调用的方法
    // 被JS调用的方法必须加入@JavascriptInterface注解
    @JavascriptInterface
    public void hello(String msg) {
        Log.e("chromium", "interface: " + msg);
    }

log

05-14 17:07:21.028 6036-6199/yjxiao.xbridge E/chromium: interface: js调用了android中的hello方法1
05-14 17:07:21.030 6036-6199/yjxiao.xbridge E/chromium: interface: js调用了android中的hello方法2
05-14 17:07:21.030 6036-6036/yjxiao.xbridge I/chromium: [INFO:CONSOLE(178)] "js侧1", source: file:///android_asset/demo.html (178)
05-14 17:07:21.032 6036-6036/yjxiao.xbridge I/chromium: [INFO:CONSOLE(180)] "js侧2", source: file:///android_asset/demo.html (180)
05-14 17:07:21.032 6036-6199/yjxiao.xbridge E/chromium: interface: js调用了android中的hello方法3
05-14 17:07:21.034 6036-6036/yjxiao.xbridge I/chromium: [INFO:CONSOLE(182)] "js侧3", source: file:///android_asset/demo.html (182)
05-14 17:07:21.035 6036-6199/yjxiao.xbridge E/chromium: interface: js调用了android中的hello方法4
05-14 17:07:21.037 6036-6036/yjxiao.xbridge I/chromium: [INFO:CONSOLE(184)] "js侧4", source: file:///android_asset/demo.html (184)
05-14 17:07:21.037 6036-6199/yjxiao.xbridge E/chromium: interface: js调用了android中的hello方法5
05-14 17:07:21.039 6036-6036/yjxiao.xbridge I/chromium: [INFO:CONSOLE(186)] "js侧5", source: file:///android_asset/demo.html (186)

试验后发现,该方式为同步调用,但js侧不会等待native侧的结果,不会阻塞js线程,即同步非阻塞。

iframe的src属性(shouldOverrideUrlLoading)

了解完js的方法执行,我们再回来看下jsbridge两次通信的问题。看一下Jsbridge 侧两次与native通信的代码

       //第一次
      function callHandler(...)
        {
          if (responseCallback) {
              //生成唯一callbackid用于标识该次jsbridge通信过程
              var callbackId = 'cb_' + (uniqueId++) + '_' + new  Date().getTime();
              responseCallbacks[callbackId] = responseCallback;
              message.callbackId = callbackId;
           }
           sendMessageQueue.push(message);
           //src:"yy://__QUEUE_MESSAGE__/"
           messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
           }
           console.log('in2 ' + CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE);
           
        //第二次
        
        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
          if (messageQueueString !== '[]') {
            bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
        }
    }

这里我们发现第一次通信jsbridge会将信息入队,会将的iframe的src属性改为"yy://QUEUE_MESSAGE/",该步骤会触发native侧的shouldoverrideurlloading 方法,试想一下,如果我们调5次该方法,会执行的逻辑应该是向sendMessageQueue这个队列里面push5个message,并会将messagingIframe.src="yy://__QUEUE_MESSAGE__/"执行5次,那会触发多少次shouldoverrideurlloading方法呢,下面我们写demo测试一下

js侧demo核心代码

function jsCallJavaSync1(i) {
        console.log("sync1: " + i);
        window.WebViewJavascriptBridge.callHandler(
            'UPWebPay', 'pay', [{
                "params": {
                    "arg1": "verify2"
                }
            }],
            function (respData) {
                console.log(respData);
                console.log(i + ' I:success');
            }, function (respData) {
                console.log(respData);
                console.log(i + ' I:success');
            });
    }
    //调用5次
    function test() {
        for (let i = 0; i < 5; i++) {
            jsCallJavaSync1(i);
        }

    }
    //调用1次
    function jsCallJavaSync() {
        window.WebViewJavascriptBridge.callHandler(
            'UPWebPay', 'pay', [{
                "params": {
                    "arg1": "verify2"
                },
                function (respData) {
                    console.log(respData);
                    console.log('2 success');
                }, function (respData) {
                    console.log(respData);
                    console.log('2 failed');
                }
            }]);
    }

native 侧shouldOverrideUrlLoading方法

public boolean shouldOverrideUrlLoading(IWebViewInterface webView, String url) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if (url.startsWith(XBridgeUtil.YY_RETURN_DATA)) {
            Log.e("chromium", "second:" + url);// 第二次返回数据
            handlerReturnData(url);
            return true;
        } else if (url.startsWith(XBridgeUtil.YY_OVERRIDE_SCHEMA)) { 
        //第一次
            Log.e("chromium", "first:" + url);
            flushMessageQueue();
            return true;
        }
        return false;
    }

调用一次执行后log如下

[INFO:CONSOLE(61)] "in2 yy://__QUEUE_MESSAGE__/", source: file:///android_asset/WebViewJavascriptBridge.js (61)
04-18 14:26:19.300 5707-5707/yjxiao.xbridge E/chromium: first:yy://__QUEUE_MESSAGE__/
04-18 14:26:19.302 5707-5707/yjxiao.xbridge E/chromium: second:"yy://return/_fetchQueue/%5B%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22msg0%22%3A%22123%22%7D%2C%7B%22msg1%22%3A%221234%22%7D%2C%7B%22test%22%3A%2212345%22%7D%5D%2C%22action%22%3A%22pay2%22%7D%2C%22callbackId%22%3A%22cb_6_1555568779298%22%7D%5D"

调用五次执行后log如下

04-18 14:19:30.028 5707-5707/yjxiao.xbridge I/chromium: [INFO:CONSOLE(63)] 
"sync1: 0", source: file:///android_asset/demo.html (63)
    [INFO:CONSOLE(61)] 
    "in2 yy://__QUEUE_MESSAGE__/", source: file:///android_asset/WebViewJavascriptBridge.js (61)
    [INFO:CONSOLE(63)] "sync1: 1", source: file:///android_asset/demo.html (63)
04-18 14:19:30.029 5707-5707/yjxiao.xbridge I/chromium: [INFO:CONSOLE(61)] 
"in2 yy://__QUEUE_MESSAGE__/", source: file:///android_asset/WebViewJavascriptBridge.js (61)
    [INFO:CONSOLE(63)] "sync1: 2", source: file:///android_asset/demo.html (63)
04-18 14:19:30.030 5707-5707/yjxiao.xbridge I/chromium: [INFO:CONSOLE(61)] 
"in2 yy://__QUEUE_MESSAGE__/", source: file:///android_asset/WebViewJavascriptBridge.js (61)
    [INFO:CONSOLE(63)] "sync1: 3", source: file:///android_asset/demo.html (63)
    [INFO:CONSOLE(61)] "in2 yy://__QUEUE_MESSAGE__/", source: file:///android_asset/WebViewJavascriptBridge.js (61)
    [INFO:CONSOLE(63)] "sync1: 4", source: file:///android_asset/demo.html (63)
    [INFO:CONSOLE(61)] "in2 yy://__QUEUE_MESSAGE__/", source: file:///android_asset/WebViewJavascriptBridge.js (61)
04-18 14:19:30.033 5707-5707/yjxiao.xbridge E/chromium: first:yy://__QUEUE_MESSAGE__/
04-18 14:19:30.036 5707-5707/yjxiao.xbridge E/chromium: second:"yy://return/_fetchQueue/%5B%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_1_1555568370028%22%7D%2C%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_2_1555568370028%22%7D%2C%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_3_1555568370029%22%7D%2C%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_4_1555568370029%22%7D%2C%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_5_1555568370029%22%7D%5D"

从log可以看出,js侧在==一次插件调用流程中,即一次事件循环中==调用callhandler一次与调用5次都只会触发native侧两次shouldoverloadingurl方法,针对这个问题,有两种可能的解释猜想

  1. 由于第一次给messagingIframe.src赋值5次,messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE,该值为一定值,是否后面四次由于与上一次一样,所以并没有触发native侧的shouldoveringurl方法
  2. 不管给messagingIframe.src赋值多少次,赋什么值,在次==一次插件调用流程中==,只有最后一次会触发shouldoverloadingurl方法,之前的都会被舍弃。

先看第一种猜想,明显不对,加载该js文件,iframe就会被创建,如果该猜想成立,那么除了第一次赋值后会成功,之后的都应该失败。接下来我们再通过实验看一下具体是怎样的。
我们修改下WebViewJavascriptBridge.js源码,将给messagingIframe.src赋值的地方,修改一下,改为每次都不一样代码如下

function callHandler(...)
        {
          if (responseCallback) {
              //生成唯一callbackid用于标识该次jsbridge通信过程
              var callbackId = 'cb_' + (uniqueId++) + '_' + new  Date().getTime();
              responseCallbacks[callbackId] = responseCallback;
              message.callbackId = callbackId;
           }
           sendMessageQueue.push(message);
           var messageQueueString = JSON.stringify(sendMessageQueue);
           //src:"yy://__QUEUE_MESSAGE__/"
           messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE+'://'+messageQueueString;
           console.log('in2 ' + messagingIframe.src);
           }

同样我们再看一下log

04-18 18:23:28.728 9826-9826/yjxiao.xbridge I/chromium:

    [INFO:CONSOLE(63)] "sync1: 0", source: file:///android_asset/demo.html (63)
    [INFO:CONSOLE(61)] "in2 yy://__QUEUE_MESSAGE__/://[{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_1_1555583008727"}]", source: file:///android_asset/WebViewJavascriptBridge.js (61)
    [INFO:CONSOLE(63)] "sync1: 1", source: file:///android_asset/demo.html (63)
04-18 18:23:28.731 9826-9826/yjxiao.xbridge I/chromium: [INFO:CONSOLE(61)] "in2 yy://__QUEUE_MESSAGE__/://[{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_1_1555583008727"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_2_1555583008728"}]", source: file:///android_asset/WebViewJavascriptBridge.js (61)
    [INFO:CONSOLE(63)] "sync1: 2", source: file:///android_asset/demo.html (63)
    [INFO:CONSOLE(61)] "in2 yy://__QUEUE_MESSAGE__/://[{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_1_1555583008727"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_2_1555583008728"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_3_1555583008730"}]", source: file:///android_asset/WebViewJavascriptBridge.js (61)
04-18 18:23:28.732 9826-9826/yjxiao.xbridge I/chromium:
    [INFO:CONSOLE(63)] "sync1: 3", source: file:///android_asset/demo.html (63)
    [INFO:CONSOLE(61)] "in2 yy://__QUEUE_MESSAGE__/://[{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_1_1555583008727"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_2_1555583008728"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_3_1555583008730"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_4_1555583008731"}]", source: file:///android_asset/WebViewJavascriptBridge.js (61)
    [INFO:CONSOLE(63)] "sync1: 4", source: file:///android_asset/demo.html (63)
04-18 18:23:28.734 9826-9826/yjxiao.xbridge I/chromium: [INFO:CONSOLE(61)] "in2 yy://__QUEUE_MESSAGE__/://[{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_1_1555583008727"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_2_1555583008728"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_3_1555583008730"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_4_1555583008731"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_5_1555583008732"}]", source: file:///android_asset/WebViewJavascriptBridge.js (61)
04-18 18:23:28.736 9826-9826/yjxiao.xbridge E/chromium: first:yy://__QUEUE_MESSAGE__/://[{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_1_1555583008727"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_2_1555583008728"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_3_1555583008730"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_4_1555583008731"},{"handlerName":"UPWebPay","data":{"args":[{"params":{"arg1":"verify2"}}],"action":"pay"},"callbackId":"cb_5_1555583008732"}]
04-18 18:23:28.742 9826-9826/yjxiao.xbridge E/chromium: second:"yy://return/_fetchQueue/%5B%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_1_1555583008727%22%7D%2C%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_2_1555583008728%22%7D%2C%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_3_1555583008730%22%7D%2C%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_4_1555583008731%22%7D%2C%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22params%22%3A%7B%22arg1%22%3A%22verify2%22%7D%7D%5D%2C%22action%22%3A%22pay%22%7D%2C%22callbackId%22%3A%22cb_5_1555583008732%22%7D%5D"

从log中我们可以清晰的看出,只有最后一次对iframe的赋值会被传到native,
再看下一次调用

04-18 18:29:15.532 9826-9826/yjxiao.xbridge E/chromium: first:yy://__QUEUE_MESSAGE__/://[{"handlerName":"UPWebPay","data":{"args":[{"msg0":"123"},{"msg1":"1234"},{"test":"12345"}],"action":"pay2"},"callbackId":"cb_7_1555583355530"}]
04-18 18:29:15.535 9826-9826/yjxiao.xbridge E/chromium: second:"yy://return/_fetchQueue/%5B%7B%22handlerName%22%3A%22UPWebPay%22%2C%22data%22%3A%7B%22args%22%3A%5B%7B%22msg0%22%3A%22123%22%7D%2C%7B%22msg1%22%3A%221234%22%7D%2C%7B%22test%22%3A%2212345%22%7D%5D%2C%22action%22%3A%22pay2%22%7D%2C%22callbackId%22%3A%22cb_7_1555583355530%22%7D%5D"

现在我们可以猜想jsbridge要使用两次通信来完成一次流程,是因为在js侧若在一次事件循环过程中出现了多次调用callHandler方法的情况,虽然每次都会给iframe的src属性赋值,但是只有最后一次会传递给native侧,前N次的数据会被丢弃。==但是其实该理由并不成立,就像我们demo里的代码就可以解决该问题,只要每次都将数据push到一个队列里面,再将整个队列作为数据传过去就不会存在该问题。因此其实两次通信是多余的,一次就可以完成==。

  • 总结
    结合我们 Android侧webview与Js通信的方式(1)一文中最开始对比的js与native侧基于webview通信的几种方式以及cordova方案,我们对jsbridge框架进行一下总结
  1. js调用native方式

jsbridge使用的是修改iframe属性,触发shouldoverloadingurl方法,该方法相对promt(),@JavascriptInterface,这两种方式,缺点在前一篇文章中已经总结过了,此处不再赘述,但是经过我们上文的分析,此处有一个优点。就是在js侧的一次事件循环过程中,无论调用多少次callhandler方法,都只会触发native侧两次(可改进为一次)shouldoverloadingurl方法。针对onJspromt()使用过程中一定要注意该方式会阻塞js线程。

  1. native调用js侧方法

jsbridge使用的是通过loadurl通过注入实现的,之前对比过该方式与evaluateJavascript()方法的优劣,evaluateJavascript()方法明显更好一些,但是于需要Android4.4以上,此处可以优化

native侧

private void loadUrl(String javascriptCommand) {
        if (mWebView != null) {
            if (sdkVersion < 19) {
                mWebView.loadUrl(javascriptCommand);
            } else {
                mWebView.evaluateJavascript(javascriptCommand, new ValueCallback() {
                    //注意,该方法返回的String多包了一个双引号,需要处理
                    @Override
                    public void onReceiveValue(String value) {
                        if (url.startsWith(XBridgeUtil.YY_RETURN_DATA)) {
                    }
                });
            }
        }
    }

js侧

function _fetchQueue(sdkVersion) {
        var 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 !== '[]') {
            if (sdkVersion > 18) {
                let url = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
                return url;
            } else {
                // console.log('in3 <=18');
                bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
            }

        }
    }
  1. 对比表

    cordova js调用native native调用js
    方式 1、promt() 2、@JavascriptInterface 1、loadurl 2、evaluateJavascript()3、confirm()
    一次事件循环中调用1次触发次数 1 1
    一次事件循环中调用N次触发次数 N N
    jsbridge未改进 js调用native native调用js
    方式 触发shouldOverrideUrlLoading loadurl
    一次事件循环中调用1次触发次数 2 2
    一次事件循环中调用N次触发次数 2 N+1
    jsbridge改进 js调用native native调用js
    方式 触发shouldOverrideUrlLoading 根据api使用loadurl或 evaluateJavascript()
    一次事件循环中调用1次触发次数 1 1
    一次事件循环中调用N次触发次数 1 N

你可能感兴趣的:(Android侧webview与Js通信的方式(2))