JsBridge原始碼分析
客戶端、H5註冊
客戶端
在BridgeWebView的初始化方法中,新增了自定義的BridgeWebViewClient,在onPageFinished方法中,去載入WebViewJavascriptBridge.js檔案(在assets目錄下):
@Overridepublic void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (BridgeWebView.toLoadJs != null) { BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs); } ... }
BridgeUtil # webViewLoadLocalJs:
public static void webViewLoadLocalJs(WebView view, String path){ String jsContent = assetFile2Str(view.getContext(), path); view.loadUrl("javascript:" + jsContent); }
H5
var doc = document; _createQueueReadyIframe(doc);var readyEvent = doc.createEvent('Events'); readyEvent.initEvent('WebViewJavascriptBridgeReady'); readyEvent.bridge = WebViewJavascriptBridge; doc.dispatchEvent(readyEvent);
H5端在使用的時候需要檢測是否初始化完畢:
if(window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); } else { document.addEventListener( 'WebViewJavascriptBridgeReady', function() { return callback(WebViewJavascriptBridge); }, false ); }
Native呼叫JS
jsBridgeWebview.callHandler(String handlerName, String data, CallBackFunction callBack);public interface CallBackFunction { public void onCallBack(String data); }
callHandler方法中呼叫doSend方法(引數:方法名; 資料; 回撥):
private void doSend(String handlerName, String data, CallBackFunction responseCallback) { Message m = new Message(); if (!TextUtils.isEmpty(data)) { m.setData(data); } if (responseCallback != null) { String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis())); responseCallbacks.put(callbackStr, responseCallback); m.setCallbackId(callbackStr); } if (!TextUtils.isEmpty(handlerName)) { m.setHandlerName(handlerName); } queueMessage(m); }
doSend方法中,將引數封裝到Message物件中(注意這個Message不是Android SDK中的Message,是該開源庫自己封裝的Message);
方法名和資料判空後放入Message物件,單獨看下回撥:
生成每個callBack唯一對應的id(其實就是一個固定格式的字串,用於儲存在Map中的key值):
String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
格式:"JAVA_CB_%s" + uniqueId + "_" + 當前時間毫秒值;uniqueId:一個自增的int型別值,預設值為0;
然後將其放入訊息佇列中(List<Message>):
private void queueMessage(Message m) { if (startupMessage != null) { startupMessage.add(m); } else { dispatchMessage(m); } }
走到這裡會看到,startupMessage在直接宣告的時候就初始化了,怎麼會為空?翻到BridgeWebViewClient類中的onPageFinished方法:
@Overridepublic void onPageFinished(WebView view, String url) { ... if (webView.getStartupMessage() != null) { for (Message m : webView.getStartupMessage()) { webView.dispatchMessage(m); } webView.setStartupMessage(null); } }
這裡可以看到,webview內容載入完之後,會遍歷這個startupMessage佇列,去呼叫BridgeWebView中的dispatchMessage方法,將Message作為引數,並且將startupMessage佇列置為null;
dispatchMessage方法:
void dispatchMessage(Message m) { String messageJson = m.toJson(); //escape special characters for json string messageJson = messageJson.replaceAll("(\\)([^utrn])", "\\\\$1$2"); messageJson = messageJson.replaceAll("(?<=[^\\])(")", "\\""); String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson); if (Thread.currentThread() == Looper.getMainLooper().getThread()) { this.loadUrl(javascriptCommand); } }
將Message物件轉為Json並做一些處理,最終轉為js命令:
"javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');",引數為json;
最終在java主執行緒中呼叫loadUrl方法,去執行WebViewJavascriptBridge.js中的方法_handleMessageFromNative;
WebViewJavascriptBridge.js
//提供給native呼叫,receiveMessageQueue 在會在頁面載入完後賦值為null,所以function _handleMessageFromNative(messageJSON) { console.log(messageJSON); if (receiveMessageQueue && receiveMessageQueue.length > 0) { receiveMessageQueue.push(messageJSON); } else { _dispatchMessageFromNative(messageJSON); } }
在webview載入完js,即onPageFinished方法後,receiveMessageQueue被置為null,所以這裡直接執行了_dispatchMessageFromNative方法;
//提供給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 { //直接傳送 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); } } } }); }
(若responseId不為空,意味著js呼叫Native並且js有回撥,這裡表明Native將js回撥需要的資料傳過來了,從回撥函式佇列里根據responseId取出回撥函式,如果該函式不為空,執行該回撥函式,並移除回撥函式佇列裡的該回撥函式;)
該方法中,首先將json引數轉為Message物件;
Native呼叫Js時沒有responseId,所以直接跳轉到else分支,判斷callbackId,即Native是否需要js返回資料,如果需要,則初始化一個回撥函式,裡面呼叫_doSend方法,將responseId和responseData封裝到message中,放入訊息佇列裡;
從messageHandlers中透過handlerName匹配對應的handler(即在Js註冊的時候設定的);執行該方法,引數為message中的data,即native中傳遞的data,以及上面初始化的回撥函式;
這裡結合H5註冊介面說一下:
WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) { ... responseCallback(responseData); // 為Native返回資料});
WebViewJavascriptBridge.js #
function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler; }
可以看到這裡在H5頁面中註冊的供Native呼叫的方法被新增到messageHandlers佇列中,key值為方法名;所以上面取出的handler即為該註冊的方法中定義的回撥方法;其中data為Java端傳來的data,responseCallback為上面初始化的function:
responseCallback = function(responseData) { _doSend({ responseId: callbackResponseId, responseData: responseData }); };
再看一下_doSend方法:
//sendMessage add message, 觸發native處理 sendMessagefunction _doSend(message, responseCallback) { ... sendMessageQueue.push(message); messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
將message物件存入sendMessageQueue佇列,改變iframe的src屬性,觸發Java端WebViewClient的shouldOverrideUrlLoading方法執行;
src=”yy://__QUEUE_MESSAGE__/”;
其實這裡是相當於Js跟Webview定義了一個協議,協議為該src,當webview攔截url時,如果是這個已經定義好的協議,則做相應的處理;
回到BridgeWebViewClient類的shouldOverrideUrlLoading方法中:
@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) { ... if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回資料 webView.handlerReturnData(url); return true; } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { // webView.flushMessageQueue(); return true; } else { return super.shouldOverrideUrlLoading(view, url); } }
攔截1:url是以""開頭的,呼叫BridgeWebView的handlerReturnData方法,這時是js為Native端返回資料;
攔截2:url是以"yy://"開頭的,呼叫BridgeWebView的flushMessageQueue方法,去重新整理訊息佇列;
這裡由於url是滿足第二條,所以先看flushMessageQueue方法:
BridgeWebView#flushMessageQueue:
void flushMessageQueue() { if (Thread.currentThread() == Looper.getMainLooper().getThread()) { loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { @Override public void onCallBack(String data) { ... } }); } }
該方法要在主執行緒執行,呼叫loadUrl方法:
private void loadUrl(String jsUrl, CallBackFunction returnCallback) { this.loadUrl(jsUrl); responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); }
JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();"
即呼叫Js的_fetchQueue方法,將建立的回撥物件,以_fetchQueue為key值存入responseCallbacks中;
觸發Js的_fetchQueue方法去返回資料,在Native去根據回撥解析資料做出相應的處理;
WebViewJavascriptBridge.js # _fetchQueue:
// 提供給native呼叫,該函式作用:獲取sendMessageQueue返回給native,由於android不能直接獲取返回的內容,所以使用url shouldOverrideUrlLoading 的方式返回內容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); }
在該方法中,首先將js需要回傳給Native的所有資料sendMessageQueue轉為json,然後觸發Native攔截url處理訊息,url = “”;
這裡攔截到該url,執行handlerReturnData方法:
BridgeWebView # handlerReturnData:
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; } }
從url中獲取js傳來的方法名以及Js傳來的值,從responseCallbacks佇列中根據方法名取出回撥函式介面物件(該回撥物件是在上面存入的key值為_fetchQueue的回撥物件),如果回撥介面物件不為空,則呼叫其onCallBack函式將js傳來的資料返回給Native端,將該回撥函式物件從responseCallbacks佇列中移除;
這裡的回撥即flushMessageQueue方法中在loadUrl中設定的回撥函式:
loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { @Override public void onCallBack(String data) { // deserializeMessage List<Message> list = null; try { list = Message.toArrayList(data); } catch (Exception e) { ... } ... // 判空 for (int i = 0; i < list.size(); i++) { Message m = list.get(i); String responseId = m.getResponseId(); // 是否是response if (!TextUtils.isEmpty(responseId)) { CallBackFunction function = responseCallbacks.get(responseId); String responseData = m.getResponseData(); function.onCallBack(responseData); responseCallbacks.remove(responseId); } else { ... } } } });
由於這裡是在js _fetchQueue方法中將整個訊息佇列都返回來了,所以需要遍歷Message集合,找到對應需要的那一條;取出responseId,我們要取的訊息是帶有responseId的(Native呼叫了js並需要回撥),這個responseId其實就是一開始java需要回撥時生成的callbackId,根據這個callbackId從responseCallbacks取出最開始存入的回撥物件 並且執行該回撥方法,在Native端拿到資料並做相應的處理;
Js呼叫Native
Native為Js註冊呼叫介面:
mBridgeWebView.registerHandler("submitFromWeb", new BridgeHandler() { @Override public void handler(String data, final CallBackFunction function) { ... } });
BridgeWebView # registerHandler:
public void registerHandler(String handlerName, BridgeHandler handler) { if (handler != null) { messageHandlers.put(handlerName, handler); } }
註冊供Js呼叫的介面方法,並設定回撥,如果回撥不為空,將回撥方法放入messageHandlers佇列中,key值為定義的方法名;
Js呼叫介面:
WebViewJavascriptBridge.callHandler('submitFromWeb', data, function(responseData) { ... });
WebViewJavascriptBridge.js中:
function callHandler(handlerName, data, responseCallback) { _doSend({ handlerName: handlerName, data: data }, responseCallback); }
返回到WebViewJavascriptBridge # _doSend方法中,將Js端的responseCallback回撥函式放入responseCallbacks佇列中,key值為生成的唯一標識callbackId,併為message物件設定callbackId,觸發Native端去過載url並攔截;
之後的動作與Native中調Js一樣,shouldOverrideUrlLoading中攔截,執行flushMessageQueue,loadUrl,Js#_fetchQueue方法,生成回撥,儲存responseCallbacks中,後面繼續執行handlerReturnData,接著看loadUrl中的回撥,這次由於js傳來的訊息沒有responseId,直接看else分支,即上文中省略號的地方:
if (!TextUtils.isEmpty(responseId)) { ... } else { CallBackFunction responseFunction = null; // if had callbackId 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 handler; if (!TextUtils.isEmpty(m.getHandlerName())) { handler = messageHandlers.get(m.getHandlerName()); } else { handler = defaultHandler; } if (handler != null){ handler.handler(m.getData(), responseFunction); } }
獲取callbackId,如果為空,直接預設不做處理;
不為空則說明Js呼叫了Native,需要Native回撥,建立一個回撥函式,最後從訊息中取出方法名去匹配Native之前註冊的方法,如果不為空,則呼叫該物件的回撥方法;
上面Native回撥不為空時建立的回撥:
responseFunction = new CallBackFunction() { @Override public void onCallBack(String data) { Message responseMsg = new Message(); responseMsg.setResponseId(callbackId); responseMsg.setResponseData(data); queueMessage(responseMsg); } };
作者:丿北緯91度灬
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1600/viewspace-2821545/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Retrofit原始碼分析三 原始碼分析原始碼
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼
- 集合原始碼分析[1]-Collection 原始碼分析原始碼
- 集合原始碼分析[3]-ArrayList 原始碼分析原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- 【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析JDK原始碼BloC
- 以太坊原始碼分析(36)ethdb原始碼分析原始碼
- 以太坊原始碼分析(38)event原始碼分析原始碼
- 以太坊原始碼分析(41)hashimoto原始碼分析原始碼
- 以太坊原始碼分析(43)node原始碼分析原始碼
- 以太坊原始碼分析(52)trie原始碼分析原始碼
- Hybrid前端jsbridge設計原理分析前端JS
- 深度 Mybatis 3 原始碼分析(一)SqlSessionFactoryBuilder原始碼分析MyBatis原始碼SQLSessionUI
- 以太坊原始碼分析(51)rpc原始碼分析原始碼RPC
- 【Android原始碼】Fragment 原始碼分析Android原始碼Fragment
- 【Android原始碼】Intent 原始碼分析Android原始碼Intent
- k8s client-go原始碼分析 informer原始碼分析(6)-Indexer原始碼分析K8SclientGo原始碼ORMIndex
- k8s client-go原始碼分析 informer原始碼分析(4)-DeltaFIFO原始碼分析K8SclientGo原始碼ORM
- 以太坊原始碼分析(20)core-bloombits原始碼分析原始碼OOM
- 以太坊原始碼分析(24)core-state原始碼分析原始碼
- 以太坊原始碼分析(29)core-vm原始碼分析原始碼
- 【MyBatis原始碼分析】select原始碼分析及小結MyBatis原始碼
- redis原始碼分析(二)、redis原始碼分析之sds字串Redis原始碼字串
- ArrayList 原始碼分析原始碼
- kubeproxy原始碼分析原始碼
- [原始碼分析]ArrayList原始碼
- redux原始碼分析Redux原始碼
- preact原始碼分析React原始碼
- Snackbar原始碼分析原始碼
- React原始碼分析React原始碼
- CAS原始碼分析原始碼
- Redux 原始碼分析Redux原始碼
- SDWebImage 原始碼分析Web原始碼
- Aspects原始碼分析原始碼
- httprouter 原始碼分析HTTP原始碼
- PowerManagerService原始碼分析原始碼
- HashSet原始碼分析原始碼