Android JsBridge源码分析

版权声明:本文为博主原创文章,转载请注明出处 https://github.com/baiiu https://blog.csdn.net/u014099894/article/details/72673438

前言

项目中一直在使用JsBridge,看了下源码,做个记录。
本篇文章先介绍用法,然后进行源码分析,因为主要是Js层不了解,所以主要对Js做了分析。

1. Java 调用 Js

1.通过注册handlerName进行通信

// Js代码中注册
WebViewJavascriptBridge.registerHandler("JavaCallJs", function(data, responseCallback) {
    document.getElementById("show").innerHTML = ("从Java端接收的数据: = " + data);
    responseCallback("Js端处理完了");
});

// Java代码中调用
mWebView.callHandler("JavaCallJs", "向Js端传入的数据", new CallBackFunction() {
    @Override public void onCallBack(String data) {
        // TODO Auto-generated method stub
        Log.i(TAG, "Js端处理完后返回数据: " + data);
    }
});

2.通过Js端DefaultHandler进行通信

// Js中通过init方法设置默认handler
bridge.init(function(message, responseCallback) {
    console.log('JS got a message', message);
    var data = {
        'Javascript Responds': '测试中文!'
    };
    responseCallback("Js端处理完后返回数据: " + data);
});

//Java代码中调用
webView.send("hello", new CallBackFunction() {
    @Override public void onCallBack(String data) {
        Log.d("moren", data);
    }
});
webView.send("hello");

2. Js 调用 Java

1.通过注册handlerName通信

// Java代码中
mWebView.registerHandler("JsCallJava", new BridgeHandler() {
    @Override public void handler(String data, CallBackFunction function) {
        Log.i(TAG, "Js端返回数据: " + data);
        function.onCallBack("Java端处理完了");
    }
});

//Js代码调用
WebViewJavascriptBridge.callHandler(
    'JsCallJava',  { 'param': '中文测试'},
    function(responseData) {
        document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
    }
);

2.通过DefaultHandler进行通信

// Java端设置默认处理Handler
mWebView.setDefaultHandler(new DefaultHandler());

//Js端调用
WebViewJavascriptBridge.send(
    data,
    function(responseData) {
        document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
    }
);

3. 源码分析

该框架的主要分析在于对Js的理解。

/*
 * 在WebView#onPageFinished时候加载该Js。
 */
(function() {
    if (window.WebViewJavascriptBridge) {
        return;
    }

    var messagingIframe; // 变更时会回调到WebView#shouldOverrideUrlLoading,此时根据制定好的规则进行分发
    var sendMessageQueue = [];
    var receiveMessageQueue = [];
    var messageHandlers = {};

    var CUSTOM_PROTOCOL_SCHEME = 'yy';
    var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/';

    var responseCallbacks = {};
    var uniqueId = 1;

    // 在加载时即创建该frame
    function _createQueueReadyIframe(doc) {
        messagingIframe = doc.createElement('iframe');
        messagingIframe.style.display = 'none';
        doc.documentElement.appendChild(messagingIframe);
    }

    // Java调用Js时,Js默认处理handler
    function init(messageHandler) {
        if (WebViewJavascriptBridge._messageHandler) {
            throw new Error('WebViewJavascriptBridge.init called twice');
        }
        WebViewJavascriptBridge._messageHandler = messageHandler;
        var receivedMessages = receiveMessageQueue;
        receiveMessageQueue = null;
        for (var i = 0; i < receivedMessages.length; i++) {
            _dispatchMessageFromNative(receivedMessages[i]);
        }
    }

    // Js call Java
    function send(data, responseCallback) {
        _doSend({
            data: data
        }, responseCallback);
    }

    function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler;
    }

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

    //sendMessage add message, 触发native处理 sendMessage, call Java
    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; // 通知native来调用_fetchQueue
    }

    // 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容
    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
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); //通过这种方式将数据传给native
    }

    //提供给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) {
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
                }
                responseCallback(message.responseData);
                delete responseCallbacks[message.responseId];
            } else {
                //直接发送,Java中rigistHandler时会生成callbackId
                if (message.callbackId) {
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        _doSend({
                            responseId: callbackResponseId,
                            responseData: responseData
                        });
                    };
                }

                var handler = WebViewJavascriptBridge._messageHandler;//先使用默认的handler
                if (message.handlerName) {
                    // js中rigistHandler时会放进来
                    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);
                    }
                }
            }
        });
    }

    //提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以
    function _handleMessageFromNative(messageJSON) {
        console.log(messageJSON);
        if (receiveMessageQueue && receiveMessageQueue.length > 0) {
            receiveMessageQueue.push(messageJSON);
        } else {
            _dispatchMessageFromNative(messageJSON);
        }
    }

    var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
        init: init,
        send: send,
        registerHandler: registerHandler,
        callHandler: callHandler,
        _fetchQueue: _fetchQueue,
        _handleMessageFromNative: _handleMessageFromNative
    };

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

4. 遇到的问题

所遇到的问题都是由iframe.src引起的:
1. iframe.src的url长度有大小限制,过大则直接会丢失
2. iframe.src的reload,即src重新复制,如果频率太快,则也会直接丢失
3. 解决完这两个问题后,在Java代码中,responseCallback这个HashMap里面的数据不用remove了,在handleReturnData里,因为上游延迟了发送。

我们解决第一个问题是将sendMessageQueue 分割发送,3个为一组,降低数据量。
解决第二个问题是 setTimeout,延迟发送。发送每一组时有一个100 + Math.ramdom()*50的间距。

这个问题类似于背压的问题,上游数据发送的太快,远远超出了iframe处理速度,所以做了个类似的buffer操作。

还有更好的解决方案:数据的变化放在window的一个对象里,直接注入JS获取。

2017/08/09,最终发现使用iframe弊端太多,timeout跟随机器的不同而不同。
于是抛弃使用iframe通信方式,使用alert来传输数据。

更改bug后的代码见GitHub#JsBridge

猜你喜欢

转载自blog.csdn.net/u014099894/article/details/72673438