源碼解讀JS與Native通訊原理-WebViewJavascriptBridge

原因:網上其實有不少講解WebViewJavascriptBridge原理的文章,但都着重了Native端,今天從一個純前端角度出發,抓住核心脈絡講解下原理,清晰明瞭,一文即懂。前端

通訊的基礎:web

  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當作參數返回。數組

clipboard.png

上圖是網上偷懶截的圖,到時候底部放個連接。app

重點: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。ide

下圖是咱們真正調用一個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端約定好的方法,執行便可。fetch

WebViewJavascriptBridge

最上面的時候說過,native能夠執行JS,能獲取到window。因此接下來重點講一下鏈接native和js的(bridge)橋樑是如何運做的。
何時初始化bridge?
重複說一下。setupWebViewJavascriptBridge第一次調用的時候,會建立一個iframe,src指向https://__bridge_loaded__。當native截獲到這個請求的時候,判斷爲bridge_loaded,就會注入一段自執行的代碼進行WebViewJavascriptBridge初始化。
接下來看代碼:url

//若是已經初始化了,則返回。
    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 = {spa

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原理解析

相關文章
相關標籤/搜索