资讯专栏INFORMATION COLUMN

源码解读JS与Native通信原理-WebViewJavascriptBridge

learn_shifeng / 936人阅读

摘要:并且最好是在的回调函数中调用,可以保证初始化成功了。当我们通知端进行初始化,并且初始化之后,里面会去遍历中的回调函数,并将当做参数注入。里面会将里面的回调函数保存在全局对象变量中则是自增的。

缘由:网上其实有很多讲解WebViewJavascriptBridge原理的文章,但都着重了Native端,今天从一个纯前端角度出发,抓住核心脉络讲解下原理,清晰明了,一文即懂。

通信的基础:

native端到js端。native能获取到window环境,执行JS。

js端到native端。native能截获H5页面跳转,故而JS端可以通过动态创建iframe来告诉native,我发请求了。(其实就是在window下维护了一个messageQueue数组,然后js创建个iframe,告诉native,我向你发请求了,但具体是什么请求,url里面是不会体现的,需要native去遍历messageQueue数组,毕竟native能取到window环境 )。

首先初始化JS端环境。

初始化方法setupWebViewJavascriptBridge,里面的字段都是约定的,因为在native执行初始化JS的时候,会用到,比如WVJBCallbacks。因为通信的过程是异步的(动态创建iframe,捕获跳转)。所以setupWebViewJavascriptBridge是异步的,并且以后调用native提供的方法也会是异步的。并且最好是在setupWebViewJavascriptBridge的回调函数中调用,可以保证初始化成功了。而回调函数里面会把WebViewJavascriptBridge当做参数返回。

上图是网上偷懒截的图,到时候底部放个链接。

重点:window.WebViewJavascriptBridge,所有的交互都是通过这个WebViewJavascriptBridge对象来完成的。初始化的过程就是动态创建一个iframe,将iframe的src设置为https://__bridge_loaded__,然后插入到页面中。前面通信基础说过,natvie能够截获h5跳转,当Native捕获到当前URL,并且其值等于 https://__bridge_loaded__ (当前URL是约定成俗的) ,就会注入一段自执行的代码(假设其为bridge,下面统一称呼了),挂载WebViewJavascriptBridge到window上。等下会着重说下这段自执行的代码是如何工作的。

if (window.WebViewJavascriptBridge), if (window.WVJBCallbacks) ,保证了初始化只执行一次。WVJBCallbacks这个字段用于还没有初始化的时候,保存回调函数。当我们通知native端进行初始化,并且初始化之后,bridge里面会去遍历WVJBCallbacks中的回调函数,并将WebViewJavascriptBridge当做参数注入。执行完后会delete window.WVJBCallbacks。

下图是我们真正调用一个native的方法,假设去获取用户信息,WebViewJavascriptBridge是重点,下面会详细讲解下这个对象。

export function getUserData() {
  return new Promise((resolve, reject) => {
    setupWebViewJavascriptBridge((WebViewJavascriptBridge) => {
      WebViewJavascriptBridge.callHandler("xxxx", params, (data) => {
        resolve(data);
      });
    })
  })
}

到了这里,其实我们前端需要做的已经完了。我再理一理顺序。getUserData,去调用native提供的方法。先调用setupWebViewJavascriptBridge,里面有做是否初始化的判断。然后回调函数里面,我们就能取到WebViewJavascriptBridge对象,对象上面挂载callHandler了方法,"xxxx"代表着和native端约定好的方法,执行即可。

WebViewJavascriptBridge

最上面的时候说过,native可以执行JS,能获取到window。所以接下来重点讲一下连接native和js的(bridge)桥梁是如何运作的。
什么时候初始化bridge?
重复说一下。setupWebViewJavascriptBridge第一次调用的时候,会创建一个iframe,src指向https://__bridge_loaded__。当native截获到这个请求的时候,判断为bridge_loaded,就会注入一段自执行的代码进行WebViewJavascriptBridge初始化。
接下来看代码:

    //如果已经初始化了,则返回。
    if (window.WebViewJavascriptBridge) {
        return;
    }
    if (!window.onerror) {
        window.onerror = function(msg, url, line) {
            console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
        }
    }
    //初始化一些属性。
    var messagingIframe;
    //用于存储消息列表
    var sendMessageQueue = [];
    //用于存储消息
    var messageHandlers = {};
    //通过下面两个协议组合来确定是否是特定的消息,然后拦击。
    var CUSTOM_PROTOCOL_SCHEME = "https";
    var QUEUE_HAS_MESSAGE = "__wvjb_queue_message__";
    //oc调用js的回调
    var responseCallbacks = {};
    //消息对应的id
    var uniqueId = 1;
    //是否设置消息超时
    var dispatchMessagesWithTimeoutSafety = true;
    //web端注册一个消息方法
    function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler;
    }
    //web端调用一个OC注册的消息
    function callHandler(handlerName, data, responseCallback) {
        if (arguments.length == 2 && typeof data == "function") {
            responseCallback = data;
            data = null;
        }
        _doSend({ handlerName: handlerName, data: data }, responseCallback);
    }
    function disableJavscriptAlertBoxSafetyTimeout() {
        dispatchMessagesWithTimeoutSafety = false;
    }
        //把消息转换成JSON字符串返回
    function _fetchQueue() {
        var messageQueueString = JSON.stringify(sendMessageQueue);
        sendMessageQueue = [];
        return messageQueueString;
    }
    //OC调用JS的入口方法
    function _handleMessageFromObjC(messageJSON) {
        _dispatchMessageFromObjC(messageJSON);
    }

    //初始化桥接对象,OC可以通过WebViewJavascriptBridge来调用JS里面的各种方法。
    window.WebViewJavascriptBridge = {
        registerHandler: registerHandler,
        callHandler: callHandler,
        disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
        _fetchQueue: _fetchQueue,
        _handleMessageFromObjC: _handleMessageFromObjC
    };


    //处理从OC返回的消息。
    function _dispatchMessageFromObjC(messageJSON) {
        if (dispatchMessagesWithTimeoutSafety) {
            setTimeout(_doDispatchMessageFromObjC);
        } else {
            _doDispatchMessageFromObjC();
        }

        function _doDispatchMessageFromObjC() {
            var message = JSON.parse(messageJSON);
            var messageHandler;
            var responseCallback;
            //回调
            if (message.responseId) {
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
                }
                responseCallback(message.responseData);
                delete responseCallbacks[message.responseId];
            } else {//主动调用
                if (message.callbackId) {
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        _doSend({ handlerName: message.handlerName, responseId: callbackResponseId, responseData: responseData });
                    };
                }
                //获取JS注册的函数
                var handler = messageHandlers[message.handlerName];
                if (!handler) {
                    console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                } else {
                    //调用JS中的对应函数处理
                    handler(message.data, responseCallback);
                }
            }
        }
    }
    //把消息从JS发送到OC,执行具体的发送操作。
    function _doSend(message, responseCallback) {
        if (responseCallback) {
            var callbackId = "cb_" + (uniqueId++) + "_" + new Date().getTime();
            //存储消息的回调ID
            responseCallbacks[callbackId] = responseCallback;
            //把消息对应的回调ID和消息一起发送,以供消息返回以后使用。
            message["callbackId"] = callbackId;
        }
        //把消息放入消息列表
        sendMessageQueue.push(message);
        //下面这句话会出发JS对OC的调用
        //让webview执行跳转操作,从而可以在
        //webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 中拦截到JS发给OC的消息
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + "://" + QUEUE_HAS_MESSAGE;
    }


    messagingIframe = document.createElement("iframe");
    messagingIframe.style.display = "none";
    //messagingIframe.body.style.backgroundColor="#0000ff";
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + "://" + QUEUE_HAS_MESSAGE;
    document.documentElement.appendChild(messagingIframe);


    //注册_disableJavascriptAlertBoxSafetyTimeout方法,让OC可以关闭回调超时,默认是开启的。
    registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
    //执行_callWVJBCallbacks方法
    setTimeout(_callWVJBCallbacks, 0);

    //初始化WEB中注册的方法。这个方法会把WEB中的hander注册到bridge中。
    //下面的代码其实就是执行WEB中的callback函数。
    function _callWVJBCallbacks() {
        var callbacks = window.WVJBCallbacks;
        delete window.WVJBCallbacks;
        for (var i = 0; i < callbacks.length; i++) {
            callbacks[i](WebViewJavascriptBridge);
        }
    }
})();

这里着重讲 JS如何调native方法的。
window.WebViewJavascriptBridge = {

    registerHandler: registerHandler,
    callHandler: callHandler,
    disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
    _fetchQueue: _fetchQueue,
    _handleMessageFromObjC: _handleMessageFromObjC
};

这个对象就是上文中回调函数获取到的WebViewJavascriptBridge 对象。callHandler表示执行某个约定的方法。
上面的源码注释很清晰了,源码就不过多解读了。接下来我们举一个完整的列子来阐述整个过程。

重点
getUserData => 调用setupWebViewJavascriptBridge => 因为是第一次调用进行初始化,会将回调函数保存到WVJBCallbacks中。
=> 动态创建ifram,native截获,注入代码进行brige初始化 => WebViewJavascriptBridge初始化 => 第一次执行所以会触发
_callWVJBCallbacks,遍历上面的WVJBCallbacks数组,并且将WebViewJavascriptBridge作为参数传入,执行回调。 => 回调中执行的就是getUserData里面的WebViewJavascriptBridge.callHandler("xxxx")
=》 callHandler执行的其实就是__doSend方法。 =》 _doSend里面会将getUserData里面的回调函数保存在全局对象变量responseCallbacks中,key则是自增的ID。并且把getUserData所调用的方法名,参数,key,都放在一个对象中(message),并将这个
message,存到另外一个全局数组变量sendMessageQueue中。 => 动态创建一个ifram,src为__wvjb_queue_message__,也就是说创建的ifram,一般就两种地址,一个是告诉native进行初始化,一个是告诉native可以轮询消息队列sendMessageQueue了。 => native拦截到URL,遍历全局变量sendMessageQueue,执行getUserData所需要的方法,这里就是XXXX,并组装参数 => 调用另一个_handleMessageFromObjC方法,解析参数,得到一开始的自增ID,从全局responseCallbacks中取到真正的回调函数执行。 => over了。完整的交互就是这个样子的了。

上面的流程已经很清楚了,如果有不懂,或者有错误的地方欢迎指正。
这是js到native端的消息过程,还有native调js的,其实过程差不多。参考下文把
WebViewJavascriptBridge原理解析

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109047.html

相关文章

  • iframe webview ,记录一次使用 jsBridge 遇到的 bug 解决过程

    摘要:通讯状态的改变次数被调用次数预计验证证实完毕。既然是因为调用间隔太短,所以就采用了切图仔常用的节流方案。随便说说纵观整个通讯过程,其实就是一个网络协议的缩影。前提-出现场景 使用机型为 Android 9,API 28 使用的 jsBridge 为 link bug 描述 在页面加载前后如果连续多次调用原生的方法,会遇到回调参数未被调用的情况。 // 多次调用如下函数, 部分 callb...

    Jensen 评论0 收藏0
  • WebViewJavascriptBridge原理解析

    摘要:否则按照正常流程处理。如果是表示是初始化环境的消息,如果是则表示是发送消息。则立即发送消息。回调主动调用获取注册的函数调用中的对应函数处理把消息从发送到,执行具体的发送操作。处理从返回的消息。从而找到具体的实现执行。 基本说明 我们的项目是一个OC与javascript重度交互的app,OC与javascript交互的那部分是在WebViewJavascriptBridge的githu...

    yck 评论0 收藏0
  • WebViewJavascriptBridge原理解析

    摘要:否则按照正常流程处理。如果是表示是初始化环境的消息,如果是则表示是发送消息。则立即发送消息。回调主动调用获取注册的函数调用中的对应函数处理把消息从发送到,执行具体的发送操作。处理从返回的消息。从而找到具体的实现执行。 基本说明 我们的项目是一个OC与javascript重度交互的app,OC与javascript交互的那部分是在WebViewJavascriptBridge的githu...

    andot 评论0 收藏0
  • WebViewJavascriptBridge原理解析

    摘要:否则按照正常流程处理。如果是表示是初始化环境的消息,如果是则表示是发送消息。则立即发送消息。回调主动调用获取注册的函数调用中的对应函数处理把消息从发送到,执行具体的发送操作。处理从返回的消息。从而找到具体的实现执行。 基本说明 我们的项目是一个OC与javascript重度交互的app,OC与javascript交互的那部分是在WebViewJavascriptBridge的githu...

    syoya 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<