開源JsBridge專案原始碼解析
什麼是JsBridge?
App開發中通過native+H5的方式實現一些開發,這就需要一箇中間元件來實現Native和H5之間的互動,這就是JsBridge。本文對 ofollow,noindex">開源JsBridge 專案的原始碼進行分析。
JsBridge互動分為native呼叫H5和H5呼叫native兩部分,該專案的類圖如下所示:

JsBridge類圖.png
BridgeWebView
BridgeWebView繼承自WebView,並實現了WebViewJavascriptBridge介面,用於載入H5。要實現native和H5之間的互通,首先需要在native和H5之間建立一種協議,雙方在通訊時都得遵循這個協議,這個協議就是"WebViewJavascriptBridge.js。BridgeWebView載入H5時首先需要載入這個js指令碼:
private void init() { this.setVerticalScrollBarEnabled(false); this.setHorizontalScrollBarEnabled(false); this.getSettings().setJavaScriptEnabled(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { WebView.setWebContentsDebuggingEnabled(true); } this.setWebViewClient(generateBridgeWebViewClient()); } protected BridgeWebViewClient generateBridgeWebViewClient() { return new BridgeWebViewClient(this); }
協議的載入就在BridgeWebViewClient.onPageFinish中
public static final String toLoadJs = "WebViewJavascriptBridge.js"; @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (BridgeWebView.toLoadJs != null) { BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs); } }
BridgeUtil.webViewLoadLocalJs:
//在H5介面載入完成後載入WebViewJavascriptBridge.js檔案 public static void webViewLoadLocalJs(WebView view, String path){ String jsContent = assetFile2Str(view.getContext(), path); view.loadUrl("javascript:" + jsContent); } public static String assetFile2Str(Context c, String urlStr){ InputStream in = null; try{ in = c.getAssets().open(urlStr); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in)); String line = null; StringBuilder sb = new StringBuilder(); do { line = bufferedReader.readLine(); if (line != null && !line.matches("^\\s*\\/\\/.*")) { // 去除註釋 sb.append(line); } } while (line != null); bufferedReader.close(); in.close(); return sb.toString(); } catch (Exception e) { e.printStackTrace(); } finally { if(in != null) { try { in.close(); } catch (IOException e) { } } } return null; }
WebViewJavascriptBridge.js到底長啥樣?
(function() { if (window.WebViewJavascriptBridge) { return; } //通過設定messagingIframe.src觸發WebView的shouldOverrideUrlLoading, //告知native H5有訊息,注意這裡並沒有攜帶訊息體 //url為yy://__QUEUE_MESSAGE__/ var messagingIframe; //bizMessagingIframe.src觸發WebView的shouldOverrideUrlLoading, //告知native H5有訊息,注意這裡會攜帶訊息體 //url為yy://://return/_fetchQueue/訊息體json字串 var bizMessagingIframe; var sendMessageQueue = [];//H5傳送給native的訊息佇列 var receiveMessageQueue = [];/H5接受到native的訊息佇列 var messageHandlers = {};//H5註冊給Js的處理器,用於處理native發來的訊息 var CUSTOM_PROTOCOL_SCHEME = 'yy'; var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'; //H5將訊息傳送給native,如果H5需要接受native的反饋,就需要提供一個callback //responseCallbacks中儲存著H5提供的回撥,以key-value的形式儲存,key對應callbackId,value對應著回撥 var responseCallbacks = {}; var uniqueId = 1; // 建立訊息index佇列iframe function _createQueueReadyIframe(doc) { messagingIframe = doc.createElement('iframe'); messagingIframe.style.display = 'none'; doc.documentElement.appendChild(messagingIframe); } //建立訊息體佇列iframe function _createQueueReadyIframe4biz(doc) { bizMessagingIframe = doc.createElement('iframe'); bizMessagingIframe.style.display = 'none'; doc.documentElement.appendChild(bizMessagingIframe); } //set default messageHandler初始化預設的訊息執行緒 function init(messageHandler) { if (WebViewJavascriptBridge._messageHandler) { throw new Error('WebViewJavascriptBridge.init called twice'); } WebViewJavascriptBridge._messageHandler = messageHandler; var receivedMessages = receiveMessageQueue; receiveMessageQueue = null; //初始化時檢視是否已經有native訊息過來了,有的話需要處理 for (var i = 0; i < receivedMessages.length; i++) { _dispatchMessageFromNative(receivedMessages[i]); } } //H5傳送data給native,提供給H5使用 function send(data, responseCallback) { _doSend({ data: data }, responseCallback); } //H5註冊處理器,native傳送訊息來的時候取handlerName對應的handler進行處理 function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler; } //ja提供給H5呼叫,指定了handlerName function callHandler(handlerName, data, responseCallback) { _doSend({ handlerName: handlerName, data: data }, responseCallback); } //H5傳送訊息給native native處理 function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); responseCallbacks[callbackId] = responseCallback; message.callbackId = callbackId; } sendMessageQueue.push(message); //觸發shouldOverrideUrlLoading="yy://__QUEUE_MESSAGE__/" messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; } // 提供給native呼叫,該函式作用:獲取sendMessageQueue返回給native // shouldOverrideUrlLoading在檢測到"yy://__QUEUE_MESSAGE__/"呼叫javascript:WebViewJavascriptBridge._fetchQueue(); function _fetchQueue() { var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; //觸發shouldOverrideUrlLoading="yy://://return/_fetchQueue/訊息體json字串" bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); } //提供給native使用,native通過"javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');"呼叫到 function _dispatchMessageFromNative(messageJSON) { setTimeout(function() { //解析native傳來的資料 var message = JSON.parse(messageJSON); var responseCallback; //native呼叫js,如果傳來的訊息有responseId,說明這個訊息是js呼叫native後native返回的 //需要回調js的callback if (message.responseId) { responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else {//native直接呼叫js的訊息 //callbackId不為空,說明需要回調native if (message.callbackId) { var callbackResponseId = message.callbackId; //通過_doSend將訊息返回給native responseCallback = function(responseData) { _doSend({ responseId: callbackResponseId, responseData: responseData }); }; } var handler = WebViewJavascriptBridge._messageHandler; //查詢H5註冊給js的handler if (message.handlerName) { handler = messageHandlers[message.handlerName]; } try { //H5註冊給js的handler,並回調responseCallback handler(message.data, responseCallback); } catch (exception) { if (typeof console != 'undefined') { console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception); } } } }); } //提供給native呼叫,receiveMessageQueue 在會在頁面載入完後賦值為null,所以 function _handleMessageFromNative(messageJSON) { console.log(messageJSON); if (receiveMessageQueue) { receiveMessageQueue.push(messageJSON); } _dispatchMessageFromNative(messageJSON); } var WebViewJavascriptBridge = window.WebViewJavascriptBridge = { init: init, send: send, registerHandler: registerHandler, callHandler: callHandler, _fetchQueue: _fetchQueue, _handleMessageFromNative: _handleMessageFromNative }; var doc = document; _createQueueReadyIframe(doc); _createQueueReadyIframe4biz(doc); var readyEvent = doc.createEvent('Events'); readyEvent.initEvent('WebViewJavascriptBridgeReady'); readyEvent.bridge = WebViewJavascriptBridge; doc.dispatchEvent(readyEvent); })();
通過WebViewJavascriptBridge.js是一個匿名自執行的js function,載入之後就能執行,從而實現native和H5之間的互動。
BridgeWebView.registerHandler
Map<String, BridgeHandler> messageHandlers = new HashMap<String, BridgeHandler>(); /** * 註冊處理程式,以便javascript呼叫它 *handlerName對應js呼叫時傳的名字 */ public void registerHandler(String handlerName, BridgeHandler handler) { if (handler != null) { // 新增至 Map<String, BridgeHandler> messageHandlers.put(handlerName, handler); } }
BridgeWebView提供registerHandler API給native使用,
例如通過如下方式註冊handler:
webView.registerHandler("submitFromWeb", new BridgeHandler() { @Override public void handler(String data, CallBackFunction function) { Log.i(TAG, "handler = submitFromWeb, data from web = " + data); function.onCallBack("submitFromWeb exe, response data 中文 from Java"); } });
註冊完成後,native就能處理H5發來的handlerName為"submitFromWeb"的訊息。同樣,function.onCallBack可以將native處理後的資料發回給H5。
WebViewJavascriptBridge.registerHandler
//H5註冊處理器,native傳送訊息來的時候取handlerName對應的handler進行處理 function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler; }
WebViewJavascriptBridge提供該API給H5使用:
bridge.registerHandler("functionInJs", function(data, responseCallback) { document.getElementById("show").innerHTML = ("data from Java: = " + data); if (responseCallback) { var responseData = "Javascript Says Right back aka!"; responseCallback(responseData); } });
通過這種方式,H5就能註冊handlerName為"functionInJs"的處理器,這樣native在傳送訊息時,如果handlerName為"functionInJs",就會觸發對應的處理器,處理完成後同樣可以將結果發回個native
BridgeWebViewClient
BridgeWebViewClient最主要如下:
//H5通過這種方式呼叫native @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { try { url = URLDecoder.decode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回資料yy://return/{function}/returncontent webView.handlerReturnData(url); return true; } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //不是返回資料yy://,就去重新整理訊息佇列,觸發 //呼叫js的_fetchQueue,這個會觸發YY_RETURN_DATA webView.flushMessageQueue(); return true; } else { return super.shouldOverrideUrlLoading(view, url); } }
H5通過設定url的形式觸發shouldOverrideUrlLoading方法,通過這種方式完成H5呼叫native
H5呼叫native的過程如下
1、native首先呼叫BridgeWebView.registerHandler註冊native的處理器:
webView.registerHandler("submitFromWeb", new BridgeHandler() { @Override public void handler(String data, CallBackFunction function) { Log.i(TAG, "handler = submitFromWeb, data from web = " + data); function.onCallBack("submitFromWeb exe, response data 中文 from Java"); } });
2、H5呼叫WebViewJavascriptBridge.send函式
function testClick() { var str1 = document.getElementById("text1").value; var str2 = document.getElementById("text2").value; //send message to native var data = {id: 1, content: "這是一個圖片 <img src=\"a.png\"/> test\r\nhahaha"}; window.WebViewJavascriptBridge.send( data , function(responseData) { document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData } ); }
在載入H5的時候已經載入了WebViewJavascriptBridge.js,所以可以直接呼叫,WebViewJavascriptBridge.send()如下所示:
//H5傳送data給native,提供給H5使用 function send(data, responseCallback) { _doSend({ data: data }, responseCallback); }
3、呼叫WebViewJavascriptBridge._doSend函式
//H5傳送訊息給native native處理 function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); responseCallbacks[callbackId] = responseCallback; message.callbackId = callbackId; } sendMessageQueue.push(message); //觸發shouldOverrideUrlLoading="yy://__QUEUE_MESSAGE__/" messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
H5在呼叫native時如果需要獲取到結果,則需要給_doSend設定responseCallback,設定後,就會給將該callback儲存到responseCallbacks[]陣列中,在呼叫native結束後,native會通過js的方式將結果回撥給H5,這時就需要從responseCallbacks取出對應的回撥函式。
_doSend將Message存放到sendMessageQueue中,並設定messagingIframe.src以呼叫WebView的shouldOverrideUrlLoading:
yy://__QUEUE_MESSAGE__
4、WebView的shouldOverrideUrlLoading檢查連結型別
webView.flushMessageQueue();
觸發 webView.flushMessageQueue:
void flushMessageQueue() { //必須正在主執行緒 if (Thread.currentThread() == Looper.getMainLooper().getThread()) { //呼叫js的"javascript:WebViewJavascriptBridge._fetchQueue();" loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { @Override public void onCallBack(String data) {//H5回撥給native //...... } }); } } public void loadUrl(String jsUrl, CallBackFunction returnCallback) { this.loadUrl(jsUrl);//native呼叫js的方法 //如果呼叫H5時需要響應H5的結果回撥,則需要將returnCallback儲存下來 responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); }
flushMessageQueue通過loadUrl呼叫javascript:WebViewJavascriptBridge._fetchQueue()獲取H5的所有資料。
5、WebViewJavascriptBridge._fetchQueue()
// 提供給native呼叫,該函式作用:獲取sendMessageQueue返回給native // shouldOverrideUrlLoading在檢測到"yy://__QUEUE_MESSAGE__/"呼叫javascript:WebViewJavascriptBridge._fetchQueue(); function _fetchQueue() { var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; //觸發shouldOverrideUrlLoading="yy://://return/_fetchQueue/訊息體json字串" bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); }
_fetchQueue將sendMessageQueue中的資料通過"yy://://return/_fetchQueue/訊息體json字串"的形式傳給shouldOverrideUrlLoading,BridgeWebView在檢測到該url後呼叫:
如果是返回資料yy://return/{function}/returncontent webView.handlerReturnData(url);
6、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; } }
在獲取到H5發來的資訊後,就查詢native對應的CallBackFunction進行處理,第4步loadUrl的時候講CallBackFunction和functionName的對應關係儲存到了responseCallbacks,這樣這裡就可以取出來進行回撥,第4步註冊的回撥如下:
new CallBackFunction() { @Override public void onCallBack(String data) {//H5回撥給native // deserializeMessage 反序列化訊息 List<Message> 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(); //H5發給native的訊息含有responseId,說明這個訊息是native呼叫H5後H5返回的,則需要回調給native的呼叫方 if (!TextUtils.isEmpty(responseId)) { //找到native對應的CallBackFunction CallBackFunction function = responseCallbacks.get(responseId); String responseData = m.getResponseData(); function.onCallBack(responseData); responseCallbacks.remove(responseId); } else {//H5呼叫native,不是響應 CallBackFunction responseFunction = null; //本次如果有回撥Id,說明native在呼叫結束後需要將結果告知H5 final String callbackId = m.getCallbackId(); if (!TextUtils.isEmpty(callbackId)) { responseFunction = new CallBackFunction() { @Override public void onCallBack(String data) { //將native處理完後的資料回撥給H5 Message responseMsg = new Message(); responseMsg.setResponseId(callbackId); responseMsg.setResponseData(data); queueMessage(responseMsg); } }; } else { responseFunction = new CallBackFunction() { @Override public void onCallBack(String data) { // do nothing } }; } //查詢handlerName對應BridgeHandler來執行H5的請求,webView.registerHandler註冊了處理器 BridgeHandler handler; if (!TextUtils.isEmpty(m.getHandlerName())) { handler = messageHandlers.get(m.getHandlerName()); } else { handler = defaultHandler; } if (handler != null){ handler.handler(m.getData(), responseFunction); } } } } });
在呼叫handler的處理函式時,如果插入了responseFunction,說明在native處理完後需要將處理結果傳個H5。這時呼叫:
//將native處理完後的資料回撥給H5 Message responseMsg = new Message(); responseMsg.setResponseId(callbackId); responseMsg.setResponseData(data); queueMessage(responseMsg);
這裡個responseMsg設定了responseId,告知H5這是個響應訊息,H5不需要再回應native了。
queueMessage->dispatchMessage
void dispatchMessage(Message m) { String messageJson = m.toJson(); //escape special characters for json string為json字串轉義特殊字元 messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2"); messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\""); messageJson = messageJson.replaceAll("(?<=[^\\\\])(\')", "\\\\\'"); String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson); // 必須要找主執行緒才會將資料傳遞出去 --- 劃重點 if (Thread.currentThread() == Looper.getMainLooper().getThread()) { Log.i("zpy","dispatchMessage:"+javascriptCommand); this.loadUrl(javascriptCommand); }
dispatchMessage通過WebView.loadUrl將響應資料交給H5。
至此,一次H5呼叫native的過程就結束了。
native呼叫H5
1、BridgeWebView提供callHandler和send兩個函式給native呼叫H5,兩種方式最終都呼叫了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); }
這裡在呼叫H5之前,儲存了callbackStr和responseCallback的對應關係,說明呼叫H5,native需要接受H5的返回結果。接著呼叫了dispatchMessage,呼叫了
"javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');"
2、WebViewJavascriptBridge._dispatchMessageFromNative
//提供給native使用,native通過"javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');"呼叫到 function _dispatchMessageFromNative(messageJSON) { setTimeout(function() { //解析native傳來的資料 var message = JSON.parse(messageJSON); var responseCallback; //native呼叫js,如果傳來的訊息有responseId,說明這個訊息是js呼叫native後native返回的 //需要回調js的callback if (message.responseId) { responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else {//native直接呼叫js的訊息 //callbackId不為空,說明需要回調native if (message.callbackId) { var callbackResponseId = message.callbackId; //通過_doSend將訊息返回給native responseCallback = function(responseData) { _doSend({ responseId: callbackResponseId, responseData: responseData }); }; } var handler = WebViewJavascriptBridge._messageHandler; //查詢H5註冊給js的handler if (message.handlerName) { handler = messageHandlers[message.handlerName]; } try { //交易H5註冊個js的handler,並回調responseCallback handler(message.data, responseCallback); } catch (exception) { if (typeof console != 'undefined') { console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception); } } } }); }
該函式提供給native呼叫,會分發native的請求,如果message.ResponseId不為空,說明該訊息是H5呼叫native後返回的資料,直接將結果返回給對應的js function即可。
如果ResponseId為空,說明這是native的一個全新的請求,這時候就需要檢視message.callbackId,callBackId不為空,說明在H5處理完請求後,需要將結果返回給native。H5處理native的請求也是通過handler實現的。那麼這個handler哪裡來的呢??
//H5註冊處理器,native傳送訊息來的時候取handlerName對應的handler進行處理 function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler; }
答案就在這邊,WebViewJavascriptBridge.js提供了registerHandler給H5註冊處理器,下面程式碼就是註冊了"functionInJs"對應的處理器。
bridge.registerHandler("functionInJs", function(data, responseCallback) { document.getElementById("show").innerHTML = ("data from Java: = " + data); if (responseCallback) { var responseData = "Javascript Says Right back aka!"; responseCallback(responseData); } });
註冊處理器中,如果有responseCallback,則需要將H5處理的結果返回,responseCallback的實現如下,他將結果通過_doSend發給了native
//通過_doSend將訊息返回給native responseCallback = function(responseData) { _doSend({ responseId: callbackResponseId, responseData: responseData }); };