Android JsBridge源码学习

众所周知Android 4.2以下的WebView存在addJavascriptInterface漏洞的问题,不太了解的同学可参考
Android4.2下 WebView的addJavascriptInterface漏洞解决方案
@JavascriptInterface
因此,公司项目中很早便使用 JsBridge 实现 “JS与Native的通信” 了。

Native与JS通信原理

Android端WebView启动时,会加载一段WebViewJavascriptBridge.js的js脚本代码。

  • Native调用JS代码:

当Native需要向JS端传递数据时,直接在Android WebView中使用WebView.loadURL(javascript:WebViewJavascriptBridge.xxxxx)调用在WebViewJavascriptBridge.js中提前定义好的xxxxx方法,将数据传递到JS端;

  • JS调用Native代码:

当JS需要将数据传递给Native时,通过JS reload iframe将数据传递到Native的shouldOverrideUrlLoading(WebView view, String url) 方法的url参数中,Android端通过截获url获取JS传递过来的参数。

以此来实现Native与JS的通信。

GitHub源码

lzyzsd/JsBridge

我注释的JsBridge

Native调用JS代码,向JS端传递数据

以下是“ Native向JS端传递数据,并接受JS回调数据 ”的时序图

sequenceDiagramparticipant BridgeWebView.java as clientAparticipant WebViewJavascriptBridge.js as serverAparticipant demo.html as serverBNote over clientA:  Native向JS端传递数据clientA-->>clientA: BridgeWebView.callHandler\n("functionInJs", \n"Native向JS问好",\n mCallBackFunction);clientA-->>clientA: doSend(handlerName, data, responseCallback)clientA-->>clientA: queueMessage(m) clientA-->>clientA: dispatchMessage(m)clientA->>serverA: BridgeWebView.loadUrl(javascriptCommand)\n调用JS的_handleMessageFromNative方法serverA-->>serverA: _handleMessageFromNative(messageJSON)serverA-->>serverA: _dispatchMessageFromNative(messageJSON)serverA->>serverB: handler(message.data, responseCallback)serverB-->>serverB: bridge.registerHandler\n("functionInJs", \nfunction(data, responseCallback))serverB-->>serverA: responseCallback(responseData)serverA-->>serverA: _doSend({responseId,responseData});serverA-->>clientA: reload iframe "yy://__QUEUE_MESSAGE__/"clientA-->>clientA: shouldOverrideUrlLoading(view, url)clientA-->>clientA: flushMessageQueue()clientA->>serverA: BridgeWebView.loadUrl(javascriptCommand)\n调用JS的_fetchQueue()方法serverA-->>serverA: _fetchQueue()serverA-->>clientA: reload iframe "yy://return/_fetchQueue/[{"data"}]"clientA-->>clientA: handlerReturnData(String url)clientA-->>clientA: flushMessageQueue中onCallBackclientA-->>clientA: mCallBackFunction.onCallBack(responseData)

BridgeWebView.java

callHandler("functionInJs", "Native向JS问好", mCallBackFunction);

/**     * Native调用JS     * 

* call javascript registered handler * 调用javascript处理程序注册 * * @param handlerName JS中注册的handlerName * @param data Native传递给JS的数据 * @param callBack JS处理完成后,回调到Native */ public void callHandler(String handlerName, String data, CallBackFunction callBack) { doSend(handlerName, data, callBack); }

注释很全,看注释吧,不作讲解

BridgeWebView.java

doSend(handlerName, data, responseCallback)

    /**     * Native 调用 JS     * 

* 保存message到消息队列 * * @param handlerName JS中注册的handlerName * @param data Native传递给JS的数据 * @param responseCallback JS处理完成后,回调到Native */ private void doSend(String handlerName, String data, CallBackFunction responseCallback) { LogUtils.e(TAG, "doSend——>data: " + data); LogUtils.e(TAG, "doSend——>handlerName: " + handlerName); // 创建一个消息体 Message m = new Message(); // 添加数据 if (!TextUtils.isEmpty(data)) { m.setData(data); } // if (responseCallback != null) { // 创建回调ID String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis())); // 1、JS回调Native数据时候使用;key: id value: callback (通过JS返回的callbackID 可以找到相应的CallBack方法) responseCallbacks.put(callbackStr, responseCallback); // 1、JS回调Native数据时候使用;key: id value: callback (通过JS返回的callbackID 可以找到相应的CallBack方法) m.setCallbackId(callbackStr); } // JS中注册的方法名称 if (!TextUtils.isEmpty(handlerName)) { m.setHandlerName(handlerName); } LogUtils.e(TAG, "doSend——>message: " + m.toJson()); // 添加消息 或者 分发消息到JS queueMessage(m); }

  • 做了一个Message来封装data数据
  • 创建了一个callBackId,并将对应的引用存储在Map responseCallbacks,这样JS相应方法处理结束后,将JS的处理结果返回来的时候,Native可通过该callbackId找到对应的CallBackFunction,从而完成数据回调。
    /**     * BridgeWebView.java     * list != null 添加到消息集合否则分发消息     *     * @param m Message     */    private void queueMessage(Message m) {        LogUtils.e(TAG, "queueMessage——>message: " + m.toJson());        if (startupMessage != null) {            startupMessage.add(m);        } else {            // 分发消息            dispatchMessage(m);        }    }    /**    * BridgeWebView.java     * 分发message 必须在主线程才分发成功     *     * @param m Message     */    void dispatchMessage(Message m) {        LogUtils.e(TAG, "dispatchMessage——>message: " + m.toJson());        // 转化为JSon字符串        String messageJson = m.toJson();        //escape special characters for json string  为json字符串转义特殊字符        messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");        messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");        String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);        LogUtils.e(TAG, "dispatchMessage——>javascriptCommand: " + javascriptCommand);        // 必须要找主线程才会将数据传递出去 --- 划重点        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {            // 调用JS中_handleMessageFromNative方法            this.loadUrl(javascriptCommand);        }    }
  • dispatchMessage中,通过load javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');将Message数据传递到JS方法的_handleMessageFromNative当中
// Native通过loadUrl(JS_HANDLE_MESSAGE_FROM_JAVA),调用JS中_handleMessageFromNative方法,实现Native向JS传递数据final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');";

WebViewJavascriptBridge.js

    // 1、收到Native的消息    // 提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以    function _handleMessageFromNative(messageJSON) {        //        console.log(messageJSON);        // 添加到消息队列        if (receiveMessageQueue) {            receiveMessageQueue.push(messageJSON);        }        // 分发Native消息        _dispatchMessageFromNative(messageJSON);           }
  • 这里将Native发送过来的消息添加到receiveMessageQueue数组中。
    //2、分发Native消息    function _dispatchMessageFromNative(messageJSON) {        setTimeout(function() {            // 解析消息            var message = JSON.parse(messageJSON);            //            var responseCallback;            //java call finished, now need to call js callback function            if (message.responseId) {                ...            } else {                // 消息中有callbackId 说明需要将处理完成后,需要回调Native端                //直接发送                if (message.callbackId) {                    // 回调消息的 回调ID                    var callbackResponseId = message.callbackId;                    //                    responseCallback = function(responseData) {                        // 发送JS端的responseData                        _doSend({                            responseId: callbackResponseId,                            responseData: responseData                        });                    };                }                var handler = WebViewJavascriptBridge._messageHandler;                if (message.handlerName) {                    handler = messageHandlers[message.handlerName];                }                //查找指定handler                try {                    handler(message.data, responseCallback);                } catch (exception) {                    if (typeof console != 'undefined') {                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);                    }                }            }        });    }

demo.html

bridge.registerHandler("functionInJs", function(data, responseCallback) {    document.getElementById("show").innerHTML = ("data from Java: = " + data);    if (responseCallback) {        var responseData = "Javascript Says Right back aka!";        responseCallback(responseData);    }});
  • 这里调用到JS的functionInJs注册方法,并将JS的处理结果Javascript Says Right back aka!返回,回调到WebViewJavascriptBridge.js _dispatchMessageFromNative注册的responseCallback,从而调用到WebViewJavascriptBridge.js 的_doSend方法之中。

一下为WebViewJavascriptBridge.js 的_doSend

WebViewJavascriptBridge.js

// 发送JS端的responseData_doSend({    responseId: callbackResponseId,    responseData: responseData});
// 3、JS将数据发送到Native端// sendMessage add message, 触发native的 shouldOverrideUrlLoading方法,使Native主动向JS取数据//// 把消息队列数据放到shouldOverrideUrlLoading 的URL中不就可以了吗?// 为什么还要Native主动取一次,然后再放到shouldOverrideUrlLoading的URL中返回?function _doSend(message, responseCallback) {    // 发送的数据存在    if (responseCallback) {        //        var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();        responseCallbacks[callbackId] = responseCallback;        message.callbackId = callbackId;    }    // 添加到消息队列中    sendMessageQueue.push(message);    // 让Native加载一个新的页面    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;}
  • 1、将Native发送过来的Message数据,存储到sendMessageQueue消息队列中
  • 2、_doSend 中 reload iframe " yy://__QUEUE_MESSAGE__/ " 触发native的 shouldOverrideUrlLoading方法

BridgeWebViewClient.java

@Override    public boolean shouldOverrideUrlLoading(WebView view, String url) {        LogUtils.d(TAG, "shouldOverrideUrlLoading——>url: " + url);        try {            url = URLDecoder.decode(url, "UTF-8");        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        }        if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据            webView.handlerReturnData(url);            return true;        } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //            webView.flushMessageQueue();            return true;        } else {            return super.shouldOverrideUrlLoading(view, url);        }    }
  • _doSend 中 reload iframe " yy://__QUEUE_MESSAGE__/ " 触发native的 shouldOverrideUrlLoading方法,最终调用到webView.flushMessageQueue();方法中
    /**     * 1、调用JS的 _fetchQueue方法,获取JS中处理后的消息队列。     * JS 中_fetchQueue 方法 中将Message数据返回到Native的 {@link #BridgeWebViewClient.shouldOverrideUrlLoading}中     * 

* 2、等待{@link #handlerReturnData} 回调 Callback方法 */ void flushMessageQueue() { LogUtils.d(TAG, "flushMessageQueue"); if (Thread.currentThread() == Looper.getMainLooper().getThread()) { // 调用JS的 _fetchQueue方法 BridgeWebView.this.loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { @Override public void onCallBack(String data) { // ... 此处暂时省略 } }); } }

  • flushMessageQueue中加载了一段JS脚本,JS_FETCH_QUEUE_FROM_JAVA,以下为JS脚本的代码。
// 调用JS的 _fetchQueue方法。_fetchQueue方法中将Message数据返回到Native的shouldOverrideUrlLoading中final static String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();";
  • 这段JS脚本代码调用到的是 WebViewJavascriptBridge.js中的 _fetchQueue方法。

WebViewJavascriptBridge.js

// 将数据返回给Native// 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容    function _fetchQueue() {        // json数据        var messageQueueString = JSON.stringify(sendMessageQueue);        // message数据清空        sendMessageQueue = [];        // 数据返回到shouldOverrideUrlLoading        //android can't read directly the return data, so we can reload iframe src to communicate with java        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);    }
  • 这里通过 reload iframe " yy://return/_fetchQueue/ + encodeURIComponent(messageQueueString)"将数据发送给Native的shouldOverrideUrlLoading方法中。
    /**     * 1、获取到CallBackFunction data执行调用并且从数据集移除     * 

* 2、回调Native{@link #flushMessageQueue()} Callback方法 * * @param url */ void handlerReturnData(String url) { LogUtils.d(TAG, "handlerReturnData——>url: " + url); // 获取js的方法名称 // _fetchQueue String functionName = BridgeUtil.getFunctionFromReturnUrl(url); // 获取_fetchQueue 对应的回调方法 CallBackFunction f = responseCallbacks.get(functionName); // 获取body Message消息体 String data = BridgeUtil.getDataFromReturnUrl(url); // 回调 Native flushMessageQueue callback方法 if (f != null) { LogUtils.d(TAG, "onCallBack data" + data); f.onCallBack(data); responseCallbacks.remove(functionName); return; } }

  • 这里的CallBackFunction 回调到了flushMessageQueue方法的onCallBack中。
@Overridepublic void onCallBack(String data) {    LogUtils.d(TAG, "flushMessageQueue——>data: " + data);    // deserializeMessage 反序列化消息    List list = null;    try {        list = Message.toArrayList(data);    } catch (Exception e) {        e.printStackTrace();        return;    }    if (list == null || list.size() == 0) {        LogUtils.e(TAG, "flushMessageQueue——>list.size() == 0");        return;    }    for (int i = 0; i < list.size(); i++) {        Message m = list.get(i);        String responseId = m.getResponseId();        /**         * 完成Native向JS发送信息后的回调         */        // 是否是response  CallBackFunction        if (!TextUtils.isEmpty(responseId)) {            CallBackFunction function = responseCallbacks.get(responseId);            String responseData = m.getResponseData();            function.onCallBack(responseData);            responseCallbacks.remove(responseId);        } else {            // ... 此处暂时省略        }    }}
  • 这里循环了从JS端获取到的Message队列,并将JS端获取的数据,回调到了Native中对应的CallBackFunction中。

到这里,JsBridge中Native调用JS代码的通信,则完成了。

一个问题

WebViewJavascriptBridge.js的_doSend(message, responseCallback)方法中,把Message消息队列 放到shouldOverrideUrlLoading 的URL中直接返回给Native不就可以了吗?

为什么还要用_doSend 中 reload iframe " yy://__QUEUE_MESSAGE__/ " 触发native的 shouldOverrideUrlLoading方法,让Native主动向JS请求一次Message队列,然后再放到shouldOverrideUrlLoading的URL中返回给Native呢?

个人观点:
觉得,这样将Message集中在一起,通过发送一个消息给Native,让Native主动将所有数据请求回来。避免了JS与Native的频繁交互。

JS调用Native代码,向Native传递数据

不太想说了,就到这吧

更多相关文章

  1. Rxjava的基础用法和源码解析(一)
  2. Android(安卓)Junit Test
  3. Android(安卓)刷新页面更新数据方法探究
  4. Android中怎么动态控制padding
  5. Android类参考---Fragment
  6. Android(安卓)纯代码加入点击效果
  7. Android(安卓)中TextView中跑马灯效果的实现方法
  8. android日记-
  9. 【073】Android(安卓)数据存储(SQLite)

随机推荐

  1. Android(安卓)JetPack——Lifecycle
  2. Android中接口(Interface)的简单使用
  3. Android(安卓)弹出菜单示例(Android(安卓)
  4. 联系人结构
  5. Flutter 、Android(安卓)的 gradle配置,使
  6. QT应用程序开发到Android
  7. ARouter 使用教程
  8. Android进程调度之adj算法
  9. Android百度地图地位当前位置和城市检索-
  10. Android(安卓)studio 快捷键汇总