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

栏目: JavaScript · 发布时间: 5年前

内容简介:缘由:网上其实有很多讲解WebViewJavascriptBridge原理的文章,但都着重了Native端,今天从一个纯前端角度出发,抓住核心脉络讲解下原理,清晰明了,一文即懂。通信的基础:初始化方法setupWebViewJavascriptBridge,里面的字段都是约定的,因为在native执行初始化JS的时候,会用到,比如WVJBCallbacks。因为通信的过程是异步的(动态创建iframe,捕获跳转)。所以setupWebViewJavascriptBridge是异步的,并且以后调用native

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

通信的基础:

  1. native端到js端。native能获取到window环境,执行JS。
  2. 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当做参数返回。

源码解读JS与Native通信原理-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原理解析

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Linear Optimization

Introduction to Linear Optimization

Dimitris Bertsimas、John N. Tsitsiklis / Athena Scientific / 1997-02-01 / USD 89.00

"The true merit of this book, however, lies in its pedagogical qualities which are so impressive..." "Throughout the book, the authors make serious efforts to give geometric and intuitive explanations......一起来看看 《Introduction to Linear Optimization》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具