jsbridge源碼分析

本文分析的是https://github.com/lzyzsd/JsBridge這個開源庫,

網(wǎng)絡(luò)上很多關(guān)于這個庫的分析,但是根據(jù)本人的理解思路,我也寫了一份分析文章.

先講一下該庫的目的或者說功能:讓js和java能夠互相調(diào)用.

那么怎么互相調(diào)用呢?

我們以這個庫提供的例子作為分析對象

java調(diào)用 js:

? webView.callHandler("functionInJs", "data from Java", new CallBackFunction() {

? @Override

? public void onCallBack(String data) {

? ? ? // TODO Auto-generated method stub

? ? ? Log.i(TAG, "reponse data from js " + data);

? }

});

對,就是這么簡單,調(diào)用了webView.callHandler就可以了,注意,這個webView.callHandler并不是源生webview的方法,而是這個庫實(shí)現(xiàn)的方法.

js調(diào)用java:

方法一:

window.WebViewJavascriptBridge.send(

? ? data

? ? , function(responseData) {

? ? ? ? document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData

? ? }

);

方法二:

window.WebViewJavascriptBridge.callHandler(

? ? 'submitFromWeb'

? ? , {'param': '中文測試'}

? ? , function(responseData) {

? ? ? ? document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData

? ? }

);

這兩種方法的的區(qū)別是方法一沒有設(shè)置handlername.方法二有設(shè)置:? ? 'submitFromWeb'

后面會講設(shè)置這個handlername有什么作用.

我們以js調(diào)用java的方法一為入口,一步一步分析怎么調(diào)用到j(luò)ava的.

方法一最后調(diào)用到WebViewJavascriptBridge.js里:

//sendMessage add message, 觸發(fā)native處理 sendMessage

function _doSend(message, responseCallback) {

? ? if (responseCallback) {

? ? ? ? var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();

? ? ? ? responseCallbacks[callbackId] = responseCallback;

? ? ? ? message.callbackId = callbackId;

? ? }

? ? sendMessageQueue.push(message);

? ? messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;

}

這里的message就是前面的data.responseCallback就是前面的function(responseData),可以看到,這個 _doSend做了三件事:

一,生成一個callbackId.并把這個callbackId與responseCallback一起存到responseCallbacks

這個其實(shí)就是生成生個鍵值對,用于保存和識別傳進(jìn)來的responseCallback.以便后面使用.

二, 調(diào)用了 sendMessageQueue.push(message);

把消息放到sendMessageQueue里.

三,? ? messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;這個是什么呢?

這個其實(shí)就是用來觸發(fā)java層的,通過修改iframe的src來觸發(fā)webviewclient的shouldOverrideUrlLoading.這個src值是yy://__QUEUE_MESSAGE__/

此時我們切到com.github.lzyzsd.jsbridge.BridgeWebViewClient#shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest):

調(diào)用到以下部分:

else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //

? ? webView.flushMessageQueue();

? ? return true;

可以看到,這里只調(diào)用了webView.flushMessageQueue();

這個方法只做了兩件事:

一,調(diào)用loadUrl(jsUrl) 觸發(fā)js調(diào)用,也就是說,我們剛從js到j(luò)ava代碼,現(xiàn)在又回到j(luò)s里了.這個jsUrl是BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA,也就是

final static String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();";

二,創(chuàng)建CallBackFunction對象,并保存到responseCallbacks里.

我們看一下js里面做了什么:

// 提供給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

? ? messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);

}

注釋里已經(jīng)寫了很清楚了,還記得剛才我們講js調(diào)到j(luò)ava的時候生成了一個message然后放到sendMessageQueue里的事嗎.

這里就是把sendMessageQueue里的message取出來,轉(zhuǎn)成json.然后拼裝到messagingIframe.src 里.此時又會觸發(fā)一次webviewclient的shouldOverrideUrlLoading:

if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回數(shù)據(jù)

? ? webView.handlerReturnData(url);

? ? return true;

由于此次src值變了.此次走到的是上面這段.注意,此時我們又回到j(luò)ava代碼了.我們看一下這個handlerReturnData的實(shí)現(xiàn):

/**

? ? * 獲取到CallBackFunction data執(zhí)行調(diào)用并且從數(shù)據(jù)集移除

? ? * @param url

? ? */

void handlerReturnData(String url) {

? String functionName = BridgeUtil.getFunctionFromReturnUrl(url);

? CallBackFunction f = responseCallbacks.get(functionName);

? String data = BridgeUtil.getDataFromReturnUrl(url);

? if (f != null) {

? ? ? f.onCallBack(data);

? ? ? responseCallbacks.remove(functionName);

? ? ? return;

? }

}

這段代碼就是取回第一次調(diào)用到shouldOverrideUrlLoading的時候我們設(shè)置的一個callback,也就是com.github.lzyzsd.jsbridge.BridgeWebView#flushMessageQueue里我們設(shè)置的new CallBackFunction()

? /**

? ? * 刷新消息隊列

? ? */

void flushMessageQueue() {

? if (Thread.currentThread() == Looper.getMainLooper().getThread()) {

? ? ? loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {

? ? ? ? @Override

? ? ? ? public void onCallBack(String data) {

? ? ? ? ? ? // deserializeMessage 反序列化消息

? ? ? ? ? ? List list = null;

? ? ? ? ? ? try {

? ? ? ? ? ? ? list = Message.toArrayList(data);

? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? ? ? if (list == null || list.size() == 0) {

? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? ? ? for (int i = 0; i < list.size(); i++) {

? ? ? ? ? ? ? Message m = list.get(i);

? ? ? ? ? ? ? String responseId = m.getResponseId();

? ? ? ? ? ? ? // 是否是response? CallBackFunction

? ? ? ? ? ? ? if (!TextUtils.isEmpty(responseId)) {

? ? ? ? ? ? ? ? ? CallBackFunction function = responseCallbacks.get(responseId);

? ? ? ? ? ? ? ? ? String responseData = m.getResponseData();

? ? ? ? ? ? ? ? ? function.onCallBack(responseData);

? ? ? ? ? ? ? ? ? responseCallbacks.remove(responseId);

? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? CallBackFunction responseFunction = null;

? ? ? ? ? ? ? ? ? // if had callbackId 如果有回調(diào)Id

? ? ? ? ? ? ? ? ? final String callbackId = m.getCallbackId();

? ? ? ? ? ? ? ? ? if (!TextUtils.isEmpty(callbackId)) {

? ? ? ? ? ? ? ? ? ? responseFunction = new CallBackFunction() {

? ? ? ? ? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? ? ? ? ? public void onCallBack(String data) {

? ? ? ? ? ? ? ? ? ? ? ? ? Message responseMsg = new Message();

? ? ? ? ? ? ? ? ? ? ? ? ? responseMsg.setResponseId(callbackId);

? ? ? ? ? ? ? ? ? ? ? ? ? responseMsg.setResponseData(data);

? ? ? ? ? ? ? ? ? ? ? ? ? queueMessage(responseMsg);

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? };

? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? responseFunction = new CallBackFunction() {

? ? ? ? ? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? ? ? ? ? public void onCallBack(String data) {

? ? ? ? ? ? ? ? ? ? ? ? ? // do nothing

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? };

? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? // BridgeHandler執(zhí)行

? ? ? ? ? ? ? ? ? BridgeHandler handler;

? ? ? ? ? ? ? ? ? if (!TextUtils.isEmpty(m.getHandlerName())) {

? ? ? ? ? ? ? ? ? ? handler = messageHandlers.get(m.getHandlerName());

? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? handler = defaultHandler;

? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? if (handler != null){

? ? ? ? ? ? ? ? ? ? handler.handler(m.getData(), responseFunction);

? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? });

? }

}

可以看出,最關(guān)鍵的調(diào)用都在這里了.我們重點(diǎn)一段一段貼出來分析一下:

String responseId = m.getResponseId();

// 是否是response? CallBackFunction

if (!TextUtils.isEmpty(responseId)) {

? CallBackFunction function = responseCallbacks.get(responseId);

? String responseData = m.getResponseData();

? function.onCallBack(responseData);

? responseCallbacks.remove(responseId);

}

先判斷message是否有responseId.這個responseId是什么呢,這個先不分析,從js調(diào)用到j(luò)ava的流程里,我們沒看到有設(shè)置這個.

final String callbackId = m.getCallbackId();

if (!TextUtils.isEmpty(callbackId)) {

? responseFunction = new CallBackFunction() {

? ? ? @Override

? ? ? public void onCallBack(String data) {

? ? ? ? Message responseMsg = new Message();

? ? ? ? responseMsg.setResponseId(callbackId);

? ? ? ? responseMsg.setResponseData(data);

? ? ? ? queueMessage(responseMsg);

? ? ? }

? };

callbackId這個我們設(shè)置了.在前面說的方法一里,

function(responseData) {

? ? ? ? document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData

? ? }

這個就是callbackId.所以這里創(chuàng)建了一個新的message.并且設(shè)置了setResponseId,然后調(diào)用了queueMessage.這個后面分析.

BridgeHandler handler;

if (!TextUtils.isEmpty(m.getHandlerName())) {

? handler = messageHandlers.get(m.getHandlerName());

} else {

? handler = defaultHandler;

}

if (handler != null){

? handler.handler(m.getData(), responseFunction);

}

這個messageHandlers又是什么呢.

我們在前面的方法一和方法二里有看到區(qū)別,其實(shí)方法一就是沒有設(shè)置messageHandlers.方法二就是有設(shè)置messageHandlers.

這個messageHandlers通過com.github.lzyzsd.jsbridge.BridgeWebView#registerHandler進(jìn)行注冊.傳入的參數(shù)是js和java雙方約定好的.

/**

* register handler,so that javascript can call it

* 注冊處理程序,以便javascript調(diào)用它

* @param handlerName handlerName

* @param handler BridgeHandler

*/

public void registerHandler(String handlerName, BridgeHandler handler) {

? if (handler != null) {

? ? ? ? ? // 添加至 Map

? ? ? messageHandlers.put(handlerName, handler);

? }

}

分析完后,我們就清楚了,假如是js調(diào)用到j(luò)ava.雙方約定一個字符串作為handlerName,然后,js通過方法二調(diào)用到j(luò)ava.這個時候,java就會通過registerHandler注冊的handler,來執(zhí)行約定的事情.

我們來看一下剛才遺留的queueMessage,

這個方法最后走到

com.github.lzyzsd.jsbridge.BridgeWebView#dispatchMessage

? /**

? ? * 分發(fā)message 必須在主線程才分發(fā)成功

? ? * @param m Message

? ? */

void dispatchMessage(Message m) {

? ? ? String messageJson = m.toJson();

? ? ? //escape special characters for json string? 為json字符串轉(zhuǎn)義特殊字符

? ? ? messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");

? ? ? messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");

? ? ? String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);

? ? ? // 必須要找主線程才會將數(shù)據(jù)傳遞出去 --- 劃重點(diǎn)

? ? ? if (Thread.currentThread() == Looper.getMainLooper().getThread()) {

? ? ? ? ? this.loadUrl(javascriptCommand);

? ? ? }

? }

也就是調(diào)用到了WebViewJavascriptBridge._handleMessageFromNative.這個在WebViewJavascriptBridge.js里.我們?nèi)タ匆幌逻@個的實(shí)現(xiàn):

//提供給native調(diào)用,receiveMessageQueue 在會在頁面加載完后賦值為null,所以

function _handleMessageFromNative(messageJSON) {

? ? console.log(messageJSON);

? ? if (receiveMessageQueue) {

? ? ? ? receiveMessageQueue.push(messageJSON);

? ? }

? ? _dispatchMessageFromNative(messageJSON);


}

這里調(diào)用到? ? _dispatchMessageFromNative(messageJSON);

//提供給native使用,

function _dispatchMessageFromNative(messageJSON) {

? ? setTimeout(function() {

? ? ? ? var message = JSON.parse(messageJSON);

? ? ? ? var responseCallback;

? ? ? ? //java call finished, now need to call js callback function

? ? ? ? if (message.responseId) {

? ? ? ? ? ? responseCallback = responseCallbacks[message.responseId];

? ? ? ? ? ? if (!responseCallback) {

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? ? ? responseCallback(message.responseData);

? ? ? ? ? ? delete responseCallbacks[message.responseId];

? ? ? ? } else {

? ? ? ? ? ? //直接發(fā)送

? ? ? ? ? ? if (message.callbackId) {

? ? ? ? ? ? ? ? var callbackResponseId = message.callbackId;

? ? ? ? ? ? ? ? responseCallback = function(responseData) {

? ? ? ? ? ? ? ? ? ? _doSend({

? ? ? ? ? ? ? ? ? ? ? ? responseId: callbackResponseId,

? ? ? ? ? ? ? ? ? ? ? ? responseData: responseData

? ? ? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? ? };

? ? ? ? ? ? }

? ? ? ? ? ? var handler = WebViewJavascriptBridge._messageHandler;

? ? ? ? ? ? if (message.handlerName) {

? ? ? ? ? ? ? ? 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);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? });

}

這個的邏輯和前面的flushMessageQueue很像

if (message.responseId) {

? ? responseCallback = responseCallbacks[message.responseId];

? ? if (!responseCallback) {

? ? ? ? return;

? ? }

? ? responseCallback(message.responseData);

? ? delete responseCallbacks[message.responseId];

}

這里的responseCallback.實(shí)際上就是方法一中js調(diào)用java時,傳入的 function(responseData),在前面分析_doSend的時候設(shè)置的.也就是調(diào)用了一次js調(diào)用java時傳入的function(responseData).

至此,js調(diào)用java就結(jié)束了.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末缰趋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子呈宇,更是在濱河造成了極大的恐慌,老刑警劉巖局雄,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甥啄,死亡現(xiàn)場離奇詭異,居然都是意外死亡炬搭,警方通過查閱死者的電腦和手機(jī)蜈漓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尚蝌,“玉大人迎变,你說我怎么就攤上這事∑裕” “怎么了衣形?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我谆吴,道長倒源,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任句狼,我火速辦了婚禮笋熬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腻菇。我一直安慰自己胳螟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布筹吐。 她就那樣靜靜地躺著糖耸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丘薛。 梳的紋絲不亂的頭發(fā)上嘉竟,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天,我揣著相機(jī)與錄音洋侨,去河邊找鬼舍扰。 笑死,一個胖子當(dāng)著我的面吹牛希坚,可吹牛的內(nèi)容都是我干的边苹。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼裁僧,長吁一口氣:“原來是場噩夢啊……” “哼勾给!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锅知,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脓钾,沒想到半個月后售睹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡可训,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年昌妹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片握截。...
    茶點(diǎn)故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡飞崖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谨胞,到底是詐尸還是另有隱情固歪,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站牢裳,受9級特大地震影響逢防,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蒲讯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一忘朝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧判帮,春花似錦局嘁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至偎痛,卻和暖如春旱捧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背踩麦。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工枚赡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谓谦。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓贫橙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親反粥。 傳聞我的和親對象是個殘疾皇子卢肃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評論 2 350

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