本文分析的是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é)束了.