彻底搞懂JsBridge的实现原理以及源码分析

3,068 阅读3分钟

JsBridge的概念

JsBridge是用来实现native和js交互通信的中间件。

H5与native交互,本质上来说就两种调用:

  1. JavaScript 调用 native 方法
  2. native 调用 JavaScript 方法

JavaScript调用native方法有两种方式:

  1. 注入,native 往 webview 的 window 对象中添加一些原生方法,h5可以通过注入的方法来调用 app 的原生能力
  2. 拦截,H5通过与 native 之间的协议发送请求,nativ e拦截请求再去调用 app 原生能力

注入

在android的实现:

public class WebAppInterface {
    // 供Js调用的方法要加JavascriptInterface注解
    @JavascriptInterface
    public int double(value) {
        return value * 2
    }
    
    @JavascriptInterface
    public int triple(value) {
        return value * 3
    }
}
webView.addJavascriptInterface(new WebAppInterface(), "android")

在JavaScript中调用

window.android.double(10) // 20

这种办法比较普遍,但是为了让JavaScript跟IOS也能通信,一般不推荐这种方式。

拦截

拦截的方式是通过拦截URL,解析约定的协议后调用原生方法,缺点是结束协议比较繁琐,传回执行之后的返回值也比较麻烦。

在H5端中,主要是通过iframe.src去发送一个url请求,这个请求是H5和native约定好的特殊协议,比如约定为yy://

在android中主要是通过shouldOverrideUrlLoading()方法来拦截URL(shouldOverrideUrlLoading()可以拦截所有webView的跳转)。如果发现URL是约定好的协议开头,那么native端就能确定H5是在试图调用原生的方法,通过解析URL中的数据去调用相关的原生能力。

native调用JavaScript方法有两种方式:

  1. loadUrl,直接使用WebView.loadUrl("javascript:function()")方法
  2. 拦截,同上

loadUrl

直接调用即可做到native调用H5

webView.loadUrl("javascript:function()");

JsBridge实现原理+源码解析

由于公司android app的JsBridge用的是github上开源的项目,所以就用这个项目去了解JaBridge是如何实现的。

在JavaScript中使用

function setupWebViewJavascriptBridge(callback) {
    // 已经注册了WebViewJavascriptBridge,就执行回调
    if (window.WebViewJavascriptBridge) {
        return callback(WebViewJavascriptBridge);
    } else {
        // WebViewJavascriptBridgeReady 事件是在 JsBridge 注册的
        document.addEventListener('WebViewJavascriptBridgeReady', function () {
            // WebView 加载完成后初始化 JsBridge
            // WebViewJavascriptBridge 也是在 JsBridge 定义的
            callback(WebViewJavascriptBridge)
        }, false);
        if (window.WVJBCallbacks) {
            return window.WVJBCallbacks.push(callback);
        }
        window.WVJBCallbacks = [callback];
        // 创建一个iframe 设置不可见并将src设置为 https://__bridge_loaded__
        // 设置在下一次宏任务中移除这个 iframe 
        // 这里创建 iframe 的目的是为了让 webview 拦截到这个请求并进行特定的操作。主要用于IOS的WKWebview
        var WVJBIframe = document.createElement('iframe');
        WVJBIframe.style.display = 'none';
        WVJBIframe.src = 'https://__bridge_loaded__';
        document.documentElement.appendChild(WVJBIframe);
        setTimeout(function () {
            document.documentElement.removeChild(WVJBIframe)
        }, 0) 
    }
}

//初始化
setupWebViewJavascriptBridge(function (bridge) {
    try {
        bridge.init(function (message, callback) {
            callback(null);
        })
    } catch (e) { }
});

export default {
    //js调APP方法 (参数分别为:app提供的方法名  传给app的数据  回调)
    callhandler: function (method, params, callback) {
        setupWebViewJavascriptBridge(function (bridge) {
            bridge.callHandler(method, params, callback)
        })
    },

    // APP调js方法 (参数分别为:js提供的方法名  回调)
    registerHandler:function (method, callback) {
        setupWebViewJavascriptBridge((bridge) => {
            bridge.registerHandler(method, (data, responseCallback) => {
                callback(data, responseCallback)
            })
        })
    }
}

JsBridge 源码解析

WebViewJavaScriptBridge.js源码
// 自执行函数
(function(){
    var messagingIframe; 
    var bizMessagingIframe;
    var sendMessageQueue = [];
    var receiveMessageQueue = [];
    var messageHandlers = {};

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

    var responseCallbacks = {};
    var uniqueId = 1;
    
    // 创建消息index队列iframe
    function _createQueueReadyIframe(doc) {
        messagingIframe = doc.createElement('iframe');
        messagingIframe.style.display = 'none';
        doc.documentElement.appendChild(messagingIframe);
    }
    //创建消息体队列iframe
    function _createQueueReadyIframe4biz(doc) {
        bizMessagingIframe = doc.createElement('iframe');
        bizMessagingIframe.style.display = 'none';
        doc.documentElement.appendChild(bizMessagingIframe);
    }
    
    //set default messageHandler  初始化默认的消息线程
    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]);
        }
    }

    // 发送
    function send(data, responseCallback) {
        _doSend({
            data: data
        }, responseCallback);
    }

    // 注册线程 往数组里面添加值
    function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler;
    }
    // 调用线程,H5中可使用bridge的方法
    function callHandler(handlerName, data, responseCallback) {
        _doSend({
            handlerName: handlerName,
            data: data
        }, responseCallback);
    }

    //sendMessage add message, 触发native处理 sendMessage
    function _doSend(message, responseCallback) {
        // 如果有回调函数,就往 responseCallback 添加该回调函数
        if (responseCallback) {
            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message.callbackId = callbackId;
        }

        sendMessageQueue.push(message);
        // 修改 messagingIframe 的src 触发 shouldOverrideUrlLoading 方法
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

    // 提供给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
        bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
    }

    //提供给native使用,
    function _dispatchMessageFromNative(messageJSON) {
        setTimeout(function() {
            var message = JSON.parse(messageJSON);
            var responseCallback;
            // 解析responseData 为 Object
            try {
                message.responseData = JSON.parse(message.responseData);
            } catch(exception) {
                if (typeof console != 'undefined') {
                    console.log("WebViewJavascriptBridge: ERROR: json parse threw.", message, exception);
                }
            }
            //java call finished, now need to call js callback function
            // android原生方法已经执行完毕,如果有回调函数就通过 responseId 在
            // responseCallbacks 中找到该方法并执行
            if (message.responseId) {
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
                }
                responseCallback(message.responseData);
                delete responseCallbacks[message.responseId];
            } else {
                // 直接发送
                // 这里为native调用js的情况
                if (message.callbackId) {
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(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);
                    }
                }
            }
        }, 0);
    }

    //提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以
    function _handleMessageFromNative(messageJSON) {
        if (receiveMessageQueue) {
            receiveMessageQueue.push(messageJSON);
        }
        _dispatchMessageFromNative(messageJSON);
       
    }
    // 在window上注册一个WebViewJavascriptBridge属性供H5使用
    var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
        init: init,
        send: send,
        registerHandler: registerHandler,
        callHandler: callHandler,
        _fetchQueue: _fetchQueue,
        _handleMessageFromNative: _handleMessageFromNative
    };

    var doc = document;
    _createQueueReadyIframe(doc);
    _createQueueReadyIframe4biz(doc);
    var readyEvent = doc.createEvent('Events');
    readyEvent.initEvent('WebViewJavascriptBridgeReady');
    readyEvent.bridge = WebViewJavascriptBridge;
    doc.dispatchEvent(readyEvent);
})()
BridgeWebViewClient.kt
// webView页面加载完毕后就加载执行WebViewJavascriptBridge.js文件
override fun onPageFinished() {
    ....
    if (BridgeWebViewKotlin.toLoadJs != null) {
        BridgeUtil.webViewLoadLocalJs(view!!, BridgeWebViewKotlin.toLoadJs)
    }
}
// 通过 shouldOverrideUrlLoading 方法去监听iframe的src的变化
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
    Log.d(TAG, "shouldOverrideUrlLoading api24")
    loadStart = false
    currentUrl = request.url.toString()
    try {
        currentUrl = URLDecoder.decode(currentUrl, "UTF-8")
    } catch (ex: UnsupportedEncodingException) {
        ex.printStackTrace()
    }

    if (currentUrl!!.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据
        // webView.flushMessageQueue() 方法执行完之后会执行 webView.handlerReturnData(currentUrl!!)
        // 执行原生方法
        webView.handlerReturnData(currentUrl!!)
        // 返回 true 证明这个 webview 不会再加载这个链接了
        return true
    } else if (currentUrl!!.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
        // 主要的作用是将 callback 添加到 responseCallbacks
        // 并且修改 bizMessagingIframe 的src 重新触发 shouldOverrideUrlLoading
        webView.flushMessageQueue()
        return true
    } else {
        // 加载这个新的链接
        return if (proxy != null) {
            proxy!!.shouldOverrideUrlLoading(view, request)
        } else {
            super.shouldOverrideUrlLoading(view, request)
        }
    }
}
BridgeWebViewKotlin.kt
/**
    * 刷新消息队列
    */
fun flushMessageQueue() {
    if (Thread.currentThread() === Looper.getMainLooper().thread) {
        // 执行 WebViewJavascriptBridge.js 中的 _fetchQueue 方法
        loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, object : CallBackFunction {
            override fun onCallBack(data: String?) {
                // deserializeMessage 反序列化消息
                var list: List<Message>?
                try {
                    list = Message.toArrayList(data)
                } catch (e: Exception) {
                    e.printStackTrace()
                    return
                }
                if (list.isEmpty()) {
                    return
                }
                for (i in list.indices) {
                    val m = list[i]
                    val responseId = m.responseId
                    // 是否是response  CallBackFunction
                    if (!TextUtils.isEmpty(responseId)) {
                        val function = responseCallbacks[responseId]
                        val responseData = m.responseData
                        function!!.onCallBack(responseData)
                        responseCallbacks.remove(responseId)
                    } else {
                        var responseFunction: CallBackFunction? = null;
                        // if had callbackId 如果有回调Id
                        val callbackId = m.callbackId
                        if (!TextUtils.isEmpty(callbackId)) {
                            responseFunction = object : CallBackFunction {
                                // 调用完原生方法调用onCallBack方法执行js的回调函数
                                override fun onCallBack(data: String?) {
                                    val responseMsg = Message()
                                    responseMsg.responseId = callbackId
                                    responseMsg.responseData = data
                                    queueMessage(responseMsg)
                                }
                            }
                        } else {
                            responseFunction = object : CallBackFunction {
                                override fun onCallBack(data: String?) {
                                    //nothing to do
                                }
                            }
                        }
                        // BridgeHandler执行
                        var handler: BridgeHandler? = if (!TextUtils.isEmpty(m.handlerName)) {
                            messageHandlers!![m.handlerName]
                        } else {
                            defaultHandler
                        }
                        handler?.handler(m.data, responseFunction)
                    }
                }

            }
        })
    }
}

详细的调用流程图为:

js调用native方法流程图.png

native调用js流程图.png

参考文章

网易大神JsSdk-移动端侧的设计开发