JsBridge源碼剖析

JsBridge是Hybrid項(xiàng)目中一種實(shí)現(xiàn)H5與Native兩者之間通訊的成熟、安全的解決方案。

基礎(chǔ)原理

JsBridge摹量,顧名思義,它是一座橋谒所,架在H5和Native之間的橋;通過(guò)這座橋沛申,H5這邊可以調(diào)用原生的方法劣领,比如調(diào)用Native的分享、相機(jī)等等這些H5無(wú)法很好滿足的功能污它;同時(shí),Native也可以通過(guò)這座橋德澈,更為方便固惯、規(guī)范地使用約定好的方法。
那JsBridge的通訊機(jī)理是什么呢屡穗?其實(shí)就是url scheme的觸發(fā)與攔截村砂。

H5調(diào)用Native

JsBridge中定義好一套u(yù)rl scheme協(xié)議屹逛,H5端根據(jù)協(xié)議觸發(fā)響應(yīng)的url罕模,頁(yè)面載體webview攔截住url后淑掌,解析發(fā)現(xiàn)是約定好的通訊協(xié)議抛腕,Native根據(jù)解析結(jié)果調(diào)用響應(yīng)的原生方法,進(jìn)行響應(yīng)的操作侥钳。

Native調(diào)用H5

那么柄错,Native是怎么調(diào)用H5中的方法的呢苦酱?不好意思,是Native創(chuàng)建了H5的載體webview颂跨,所以Native是爸爸扯饶,它可以直接調(diào)用頁(yè)面中的全局函數(shù)方法。但是既然使用了JsBridge钓丰,那么肯定是要按照協(xié)議執(zhí)行的携丁,“執(zhí)法辦事”是必要的梦鉴,不然代碼沒(méi)有規(guī)范肥橙,難以落成文檔存筏,后期迭代維護(hù)成本會(huì)遞增方篮。
在使用JsBridge的情況下藕溅,JsBridge將會(huì)掛載在頁(yè)面的window變量中,H5將Native所需調(diào)用的方法注冊(cè)到JsBridge中汁掠;Native然后根據(jù)約定的方式對(duì)這些方法進(jìn)行傳參和調(diào)用,具體實(shí)現(xiàn)會(huì)在下面源碼解析板塊進(jìn)行解讀集币。

源碼剖析

JsBridge的源碼不多,不到200行鞠苟,下面我們按照“自己手寫(xiě)一個(gè)JsBridge”的思路來(lái)對(duì)源碼進(jìn)行搜索拆分。

H5調(diào)用Native

根據(jù)事先機(jī)理当娱,我首先想到的是吃既,把實(shí)現(xiàn)H5調(diào)用Native的代碼捋出來(lái)跨细。那么鹦倚,我們需要在頁(yè)面中來(lái)觸發(fā)url scheme。我們會(huì)想到window.location.href冀惭、a標(biāo)簽或者iframe等等震叙。源碼中使用的是iframe.src來(lái)觸發(fā),為什么呢媒楼?因?yàn)閣indow.location.href和a標(biāo)簽其實(shí)是一樣,其如果短時(shí)間內(nèi)觸發(fā)多次匣砖,webview只會(huì)捕獲最后一次請(qǐng)求而忽略之前的 ,這個(gè)解釋援引搜索內(nèi)容拂共,我沒(méi)有去進(jìn)行驗(yàn)證,有興趣的同學(xué)可以驗(yàn)證下哦宜狐。下面先看第一段代碼咱台,描述我就直接寫(xiě)在在代碼的注釋中了。

var messagingIframe; // 觸發(fā)url scheme的iframe
var bizMessagingIframe; // 又定義了一個(gè)俭驮,這個(gè)我們不管回溺,再往下看看
var sendMessageQueue = []; // 存放H5向Native發(fā)送的消息隊(duì)列
var receiveMessageQueue = []; // 存放Native發(fā)送給H5的消息
var messageHandlers = {};  // 可供native調(diào)用的方法,通過(guò)registerHandler注冊(cè)存入messageHandlers

var CUSTOM_PROTOCOL_SCHEME = 'yy'; // url scheme中用以標(biāo)志協(xié)議的字段
var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'; // url scheme中另一個(gè)字段

var responseCallbacks = {}; // 存放callback的對(duì)象
var uniqueId = 1; // 用于生成callbackId的標(biāo)志之一混萝,每次生成后都會(huì)加1

有了進(jìn)行通訊的url遗遵,我們下面就來(lái)看看是怎么進(jìn)行觸發(fā)的吧。掛載window上的WebViewJavascriptBridge中定義一些方法逸嘀,callHandler就是用來(lái)給H5調(diào)用Native使用的车要。

var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
    init: init,  // 初始化
    send: send, // 單純的H5發(fā)送消息
    registerHandler: registerHandler, // 用以注冊(cè)Native需要調(diào)用的js方法
    callHandler: callHandler, // 發(fā)送消息,并指明需要調(diào)用的Native方法
    _fetchQueue: _fetchQueue, //  Native調(diào)用崭倘,用以獲取sendMessageQueue中的消息
    _handleMessageFromNative: _handleMessageFromNative // Native調(diào)用翼岁,給H5發(fā)消息
};

callHandler只是對(duì)_doSend函數(shù)的簡(jiǎn)單封裝,看來(lái)具體的實(shí)現(xiàn)是在_doSend中

function callHandler(handlerName, data, responseCallback) {
    _doSend({ // 發(fā)送的message包含調(diào)用的native方法名稱(chēng)以及傳輸?shù)臄?shù)據(jù)
        handlerName: handlerName,
        data: data
    }, responseCallback);
}

/**
 * [_doSend H5調(diào)用native,對(duì)callHandler的豐富]
 * @param       {[type]} message          [調(diào)用消息體]
 * @param       {[type]} responseCallback [回調(diào)函數(shù)]
 * @constructor
 * @return      {[type]}                  [description]
 */
function _doSend(message, responseCallback) {
  // 如果傳入回調(diào)函數(shù)司光,則為回調(diào)函數(shù)生成一個(gè)callbackId登澜,
  // 以callbackId為key,將回調(diào)函數(shù)存入responseCallbacks對(duì)象中,用于之后回調(diào)使用
    if (responseCallback) {
        var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); // callbackId
        responseCallbacks[callbackId] = responseCallback; // 將callbackId維護(hù)字在responseCallbacks中
        message.callbackId = callbackId; // message新加一個(gè)callbackId字段
        // 當(dāng)前message包含內(nèi)容
        // {
        //   callbackId: callbackId, // 回調(diào)函數(shù)id
        //   handlerName: handlerName, // 調(diào)用的Native方法名
        //   data: data // 傳參數(shù)據(jù)
        // }
    }
    sendMessageQueue.push(message);
    // 通過(guò)iframe通知客戶端有消息了
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; 
}

這邊我們便可以來(lái)解釋下最初為啥要?jiǎng)?chuàng)建兩個(gè)iframe。messagingIframe并不是用來(lái)傳輸消息的飘庄,而只是告訴Native有消息需要處理了,真正用來(lái)傳輸消息的iframe是bizMessagingIframe购撼。

// 提供給native調(diào)用,該函數(shù)作用:獲取sendMessageQueue返回給native,由于android不能直接獲取返回的內(nèi)容,所以使用url shouldOverrideUrlLoading 的方式返回內(nèi)容
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得到通知后跪削,通過(guò)_fetchQueue方法將sendMessageQueue中消息取出并通過(guò)bizMessagingIframe觸發(fā)url scheme來(lái)傳輸消息。說(shuō)實(shí)話迂求,這邊的實(shí)現(xiàn)有點(diǎn)迷糊碾盐,感覺(jué)沒(méi)必要多中間通知這個(gè)環(huán)節(jié),可能作者另有考慮吧揩局。

Native調(diào)用H5

WebViewJavascriptBridge中定義的_handleMessageFromNative就是用來(lái)處理Native對(duì)H5的調(diào)用

// Native調(diào)用H5主要分為兩種
// 1毫玖、H5調(diào)用Native時(shí),傳入的callback,Native執(zhí)行完后付枫,現(xiàn)在來(lái)執(zhí)行回調(diào)
// 2烹玉、Native調(diào)用H5方法,可以選擇傳入需要回調(diào)的Native函數(shù)
function _dispatchMessageFromNative(messageJSON) {
    setTimeout(function() { // 異步掛起處理阐滩,不會(huì)影響同步任務(wù)
        var message = JSON.parse(messageJSON);
        var responseCallback;
        if (message.responseId) { // 如果消息中有responseId二打,代表這是native執(zhí)行完,這個(gè)responseId其實(shí)就是_doSend函數(shù)中傳入的callbackId
            responseCallback = responseCallbacks[message.responseId]; // 根據(jù)responseId取出之前存入responseCallbacks對(duì)象中的回調(diào)函數(shù)
            if (!responseCallback) {
                return;
            }
            responseCallback(message.responseData); // 傳入message.responseData參數(shù)掂榔,執(zhí)行callback
            delete responseCallbacks[message.responseId]; // callback執(zhí)行完后继效,從responseCallbacks隊(duì)列中刪除該callback
        } else { // native調(diào)用H5的方法
        // Native調(diào)用H5方法過(guò)程與之前類(lèi)似,也可以要求有個(gè)回調(diào)
            if (message.callbackId) { // callbackId代表装获,native要求H5方法執(zhí)行完后瑞信,給native一個(gè)回調(diào)
                var callbackResponseId = message.callbackId;
                responseCallback = function(responseData) {
                    _doSend({
                        responseId: callbackResponseId, // responseId告訴native這是你要的回調(diào)
                        responseData: responseData
                    });
                };
            }

            var handler = WebViewJavascriptBridge._messageHandler; // init時(shí)定義的H5默認(rèn)方法,如果native沒(méi)有指明handlerName的情況下穴豫,就會(huì)調(diào)用默認(rèn)方法
            if (message.handlerName) { // native指定了要調(diào)用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之間通訊的主體實(shí)現(xiàn)代碼就已經(jīng)講完了。還是蠻簡(jiǎn)單的绩郎,其實(shí)就是定義了兩者通訊的一種協(xié)議潘鲫。

最后

年后本想著寫(xiě)一篇文章來(lái)闡述Hybrid實(shí)現(xiàn)原理的完整技術(shù)解讀,奈何在原生部分有短板肋杖,就只能寫(xiě)個(gè)JsBridge來(lái)個(gè)源碼解析來(lái)解解饞了~

參考

  1. JSBridge的原理與實(shí)現(xiàn)
  2. WebViewJavascriptBridge.js
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末溉仑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子状植,更是在濱河造成了極大的恐慌浊竟,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件津畸,死亡現(xiàn)場(chǎng)離奇詭異振定,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)肉拓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)后频,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人暖途,你說(shuō)我怎么就攤上這事卑惜。” “怎么了驻售?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵露久,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我欺栗,道長(zhǎng)毫痕,這世上最難降的妖魔是什么征峦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮消请,結(jié)果婚禮上栏笆,老公的妹妹穿的比我還像新娘。我一直安慰自己梯啤,他們只是感情好竖伯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著因宇,像睡著了一般七婴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上察滑,一...
    開(kāi)封第一講書(shū)人閱讀 51,231評(píng)論 1 299
  • 那天打厘,我揣著相機(jī)與錄音,去河邊找鬼贺辰。 笑死户盯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饲化。 我是一名探鬼主播莽鸭,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吃靠!你這毒婦竟也來(lái)了硫眨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤巢块,失蹤者是張志新(化名)和其女友劉穎礁阁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體族奢,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姥闭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了越走。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棚品。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖廊敌,靈堂內(nèi)的尸體忽然破棺而出南片,到底是詐尸還是另有隱情,我是刑警寧澤庭敦,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站薪缆,受9級(jí)特大地震影響秧廉,放射性物質(zhì)發(fā)生泄漏伞广。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一疼电、第九天 我趴在偏房一處隱蔽的房頂上張望嚼锄。 院中可真熱鬧,春花似錦蔽豺、人聲如沸区丑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)沧侥。三九已至,卻和暖如春魄鸦,著一層夾襖步出監(jiān)牢的瞬間宴杀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工拾因, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旺罢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓绢记,卻偏偏與公主長(zhǎng)得像扁达,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蠢熄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 原文地址 前言 Web 頁(yè)面中的 JS 與 iOS Native 如何交互是每個(gè) iOS 猿必須掌握的技能跪解。而 J...
    小嘴冰涼別亂親閱讀 1,867評(píng)論 2 5
  • 鏈接:http://www.reibang.com/p/fd61e8f4049e 一、簡(jiǎn)介 這部分主要介紹下 W...
    柒黍閱讀 1,818評(píng)論 0 4
  • 什么是JsBridge护赊? App開(kāi)發(fā)中通過(guò)native+H5的方式實(shí)現(xiàn)一些開(kāi)發(fā)惠遏,這就需要一個(gè)中間組件來(lái)實(shí)現(xiàn)Nati...
    JasmineBen閱讀 1,376評(píng)論 0 4
  • 在移動(dòng)app中經(jīng)常需要將一些NA較難實(shí)現(xiàn)的但是邏輯不復(fù)雜的UI如圖表,或者只是單純的數(shù)據(jù)展示的簡(jiǎn)單頁(yè)面交由h5來(lái)完...
    白六小子閱讀 2,127評(píng)論 0 2
  • 2016的新年骏啰,那時(shí)剛過(guò)十八节吮,我就很明顯地意識(shí)到,很多東西將一點(diǎn)一點(diǎn)地流失掉判耕。 正逢喬遷新居透绩,老住處的一幕幕在腦海...
    舊海城閱讀 252評(píng)論 0 0