美文网首页前端杂货铺
JsBridge源码剖析

JsBridge源码剖析

作者: loosenRogers | 来源:发表于2019-03-17 23:39 被阅读0次

    JsBridge是Hybrid项目中一种实现H5与Native两者之间通讯的成熟、安全的解决方案。

    基础原理

    JsBridge,顾名思义,它是一座桥,架在H5和Native之间的桥;通过这座桥,H5这边可以调用原生的方法,比如调用Native的分享、相机等等这些H5无法很好满足的功能;同时,Native也可以通过这座桥,更为方便、规范地使用约定好的方法。
    那JsBridge的通讯机理是什么呢?其实就是url scheme的触发与拦截。

    H5调用Native

    JsBridge中定义好一套url scheme协议,H5端根据协议触发响应的url,页面载体webview拦截住url后,解析发现是约定好的通讯协议,Native根据解析结果调用响应的原生方法,进行响应的操作。

    Native调用H5

    那么,Native是怎么调用H5中的方法的呢?不好意思,是Native创建了H5的载体webview,所以Native是爸爸,它可以直接调用页面中的全局函数方法。但是既然使用了JsBridge,那么肯定是要按照协议执行的,“执法办事”是必要的,不然代码没有规范,难以落成文档,后期迭代维护成本会递增。
    在使用JsBridge的情况下,JsBridge将会挂载在页面的window变量中,H5将Native所需调用的方法注册到JsBridge中;Native然后根据约定的方式对这些方法进行传参和调用,具体实现会在下面源码解析板块进行解读。

    源码剖析

    JsBridge的源码不多,不到200行,下面我们按照“自己手写一个JsBridge”的思路来对源码进行搜索拆分。

    H5调用Native

    根据事先机理,我首先想到的是,把实现H5调用Native的代码捋出来。那么,我们需要在页面中来触发url scheme。我们会想到window.location.href、a标签或者iframe等等。源码中使用的是iframe.src来触发,为什么呢?因为window.location.href和a标签其实是一样,其如果短时间内触发多次,webview只会捕获最后一次请求而忽略之前的 ,这个解释援引搜索内容,我没有去进行验证,有兴趣的同学可以验证下哦。下面先看第一段代码,描述我就直接写在在代码的注释中了。

    var messagingIframe; // 触发url scheme的iframe
    var bizMessagingIframe; // 又定义了一个,这个我们不管,再往下看看
    var sendMessageQueue = []; // 存放H5向Native发送的消息队列
    var receiveMessageQueue = []; // 存放Native发送给H5的消息
    var messageHandlers = {};  // 可供native调用的方法,通过registerHandler注册存入messageHandlers
    
    var CUSTOM_PROTOCOL_SCHEME = 'yy'; // url scheme中用以标志协议的字段
    var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'; // url scheme中另一个字段
    
    var responseCallbacks = {}; // 存放callback的对象
    var uniqueId = 1; // 用于生成callbackId的标志之一,每次生成后都会加1
    

    有了进行通讯的url,我们下面就来看看是怎么进行触发的吧。挂载window上的WebViewJavascriptBridge中定义一些方法,callHandler就是用来给H5调用Native使用的。

    var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
        init: init,  // 初始化
        send: send, // 单纯的H5发送消息
        registerHandler: registerHandler, // 用以注册Native需要调用的js方法
        callHandler: callHandler, // 发送消息,并指明需要调用的Native方法
        _fetchQueue: _fetchQueue, //  Native调用,用以获取sendMessageQueue中的消息
        _handleMessageFromNative: _handleMessageFromNative // Native调用,给H5发消息
    };
    

    callHandler只是对_doSend函数的简单封装,看来具体的实现是在_doSend中

    function callHandler(handlerName, data, responseCallback) {
        _doSend({ // 发送的message包含调用的native方法名称以及传输的数据
            handlerName: handlerName,
            data: data
        }, responseCallback);
    }
    
    /**
     * [_doSend H5调用native,对callHandler的丰富]
     * @param       {[type]} message          [调用消息体]
     * @param       {[type]} responseCallback [回调函数]
     * @constructor
     * @return      {[type]}                  [description]
     */
    function _doSend(message, responseCallback) {
      // 如果传入回调函数,则为回调函数生成一个callbackId,
      // 以callbackId为key,将回调函数存入responseCallbacks对象中,用于之后回调使用
        if (responseCallback) {
            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); // callbackId
            responseCallbacks[callbackId] = responseCallback; // 将callbackId维护字在responseCallbacks中
            message.callbackId = callbackId; // message新加一个callbackId字段
            // 当前message包含内容
            // {
            //   callbackId: callbackId, // 回调函数id
            //   handlerName: handlerName, // 调用的Native方法名
            //   data: data // 传参数据
            // }
        }
        sendMessageQueue.push(message);
        // 通过iframe通知客户端有消息了
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; 
    }
    

    这边我们便可以来解释下最初为啥要创建两个iframe。messagingIframe并不是用来传输消息的,而只是告诉Native有消息需要处理了,真正用来传输消息的iframe是bizMessagingIframe。

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

    Native得到通知后,通过_fetchQueue方法将sendMessageQueue中消息取出并通过bizMessagingIframe触发url scheme来传输消息。说实话,这边的实现有点迷糊,感觉没必要多中间通知这个环节,可能作者另有考虑吧。

    Native调用H5

    WebViewJavascriptBridge中定义的_handleMessageFromNative就是用来处理Native对H5的调用

    // Native调用H5主要分为两种
    // 1、H5调用Native时,传入的callback,Native执行完后,现在来执行回调
    // 2、Native调用H5方法,可以选择传入需要回调的Native函数
    function _dispatchMessageFromNative(messageJSON) {
        setTimeout(function() { // 异步挂起处理,不会影响同步任务
            var message = JSON.parse(messageJSON);
            var responseCallback;
            if (message.responseId) { // 如果消息中有responseId,代表这是native执行完,这个responseId其实就是_doSend函数中传入的callbackId
                responseCallback = responseCallbacks[message.responseId]; // 根据responseId取出之前存入responseCallbacks对象中的回调函数
                if (!responseCallback) {
                    return;
                }
                responseCallback(message.responseData); // 传入message.responseData参数,执行callback
                delete responseCallbacks[message.responseId]; // callback执行完后,从responseCallbacks队列中删除该callback
            } else { // native调用H5的方法
            // Native调用H5方法过程与之前类似,也可以要求有个回调
                if (message.callbackId) { // callbackId代表,native要求H5方法执行完后,给native一个回调
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        _doSend({
                            responseId: callbackResponseId, // responseId告诉native这是你要的回调
                            responseData: responseData
                        });
                    };
                }
    
                var handler = WebViewJavascriptBridge._messageHandler; // init时定义的H5默认方法,如果native没有指明handlerName的情况下,就会调用默认方法
                if (message.handlerName) { // native指定了要调用H5方法
                    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);
                    }
                }
            }
        });
    }
    

    至此,JsBridge中H5与Native之间通讯的主体实现代码就已经讲完了。还是蛮简单的,其实就是定义了两者通讯的一种协议。

    最后

    年后本想着写一篇文章来阐述Hybrid实现原理的完整技术解读,奈何在原生部分有短板,就只能写个JsBridge来个源码解析来解解馋了~

    参考

    1. JSBridge的原理与实现
    2. WebViewJavascriptBridge.js

    相关文章

      网友评论

        本文标题:JsBridge源码剖析

        本文链接:https://www.haomeiwen.com/subject/jxwumqtx.html