JsBridge源码详解(一) JS与Native通讯过程(附详细流程图)

时间:2019-04-19
本文章向大家介绍JsBridge源码详解(一) JS与Native通讯过程(附详细流程图),主要包括JsBridge源码详解(一) JS与Native通讯过程(附详细流程图)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

通讯前准备

通讯的实现需要注入一段js代码,js代码的注入在页面加载完毕也就是WebViewClent的onPageFinished方法中

@Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(mWebView, url);
        //加载本地通讯桥接的js文件
        JsBridgeUtil.webViewLoadLocalJs(view, BridgeX5WebView.toLoadJs);
    }

其中JsBridgeUtil的webViewLoadLocalJs方法负责注入js片段,其代码如下

public static void webViewLoadLocalJs(WebView view, String path) {
        String jsContent = assetFile2Str(view.getContext(), path);
        view.loadUrl("javascript:" + jsContent);
    }

负责通讯的js片段以文件形式放在asserts目录下,assetFile2Str将文件以流的方式读取出来转换成字符串格式,然后通过loadUrl就加载了通讯的js片段,js的具体内容下面再一一分析。

至此,通讯的准备工作就完成了,下面来看看具体的通讯实现。

通讯过程

js调用native有个前提条件,就是上面讲到的js片段必须注入完毕,所以这里有部分逻辑是对该部分准备工作的监听,负责通讯的js通过发送事件的方式告诉调用方js:

 var readyEvent = doc.createEvent('Events');
    readyEvent.initEvent('WebViewJavascriptBridgeReady');
    readyEvent.bridge = WebViewJavascriptBridge;
    doc.dispatchEvent(readyEvent);

调用方js可以通过如下方式来监听该事件

function connectWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) {
                // 准备完毕,可以通讯了
            } else {
                document.addEventListener(
                    'WebViewJavascriptBridgeReady'
                    , function() {
                        // 准备完毕,可以通讯了
                    },
                    false
                );
            }
        }

监听到桥js加载完毕(步骤1),接下来就可以通讯了,调用方js通过调用桥js的callHandler函数来发起通讯(步骤2),来看一个获取H5所运行的设备的信息例子:

function getDeviceInfo(){
             window.WebViewJavascriptBridge.callHandler(
                'NativeHandler'
                , {'funcName'  : 'getDeviceInfo'}
                , function(responseData) {
                    document.getElementById("show").innerHTML = "getDeviceInfo involved,responseData from native is " + responseData
                }
            );
        }

各个参数的作用后面会讲到,先来看一下callHandler的实现

function callHandler(handlerName, data, responseCallback) {
        _doSend({
            handlerName: handlerName,
            data: data
        }, responseCallback);
    }

callHandler调用了内部doSend方法

 function _doSend(message, responseCallback) {
        if (responseCallback) {
            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message.callbackId = callbackId;
        }
        sendMessageQueue.push(message);
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

dosend方法先是判断是否需要有结果回调,如果有则生成唯一的回调id并将该id与回调方法一起保存进map以便调用结束后通知回调方法。回调id的生成用一个自增长的整数和当前时间戳组成。唯一的id会被放进传递给native的message对象中,使得native执行完毕之后可以告诉js应该将结果返回给哪个id对应的回调方法。

需要注意的是,dosend的方法并没有直接将调用者发送来的数据告诉native,而是将message对象保存进待发送消息队列sendMessageQueue,然后改变iframe标签的src来通知native有消息要取,这是怎么做到的呢?

首先这里要简单介绍下iframe:

iframe 标签规定一个内联框架

一个内联框架被用来在当前 HTML 文档中嵌入另一个文档

iframe能在当前页面加载其他的页面,也就是说制定了其src之后会触发WebViewClient的shouldOverrideUrlLoading方法。

看一下iframe的初始化

var doc = document;
_createQueueReadyIframe(doc);

 function _createQueueReadyIframe(doc) {
        messagingIframe = doc.createElement('iframe');
        messagingIframe.style.display = 'none';
        doc.documentElement.appendChild(messagingIframe);
    }

在documentElement上增加了一个不可见的iframe标签,使用该标签来触发通讯。那么究竟是如何通讯的呢?

看一下native的WebViewClient的shouldOverrideUrlLoading方法(步骤3)

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith(JsBridgeUtil.YY_RETURN_DATA)) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            LogUtil.e("decode url fail:" + url, e);
        }
        mWebView.handlerReturnData(url);
        return true;
    } else if (url.startsWith(JsBridgeUtil.YY_OVERRIDE_SCHEMA)) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            LogUtil.e("decode url fail:" + url, e);
        }
        mWebView.flushMessageQueue();
        return true;
    } else {
        return super.shouldOverrideUrlLoading(view, url);
    }
}

其中JsBridgeUtil.YY_RETURN_DATA 为 ”yy://return/“这部分用来处理从js端获取到的返回数据。

JsBridgeUtil.YY_OVERRIDE_SCHEMA为”yy://“ 刚刚桥js的doSend方法改变的iframe的src为CUSTOM_PROTOCOL_SCHEME + ‘