接上篇
JsBridge
1.使用方法
1.1,java調用js指定方法,并有回調:
//java端的代碼
webView.callHandler("functionInJs", param, new CallBackFunction() {
@Override
public void onCallBack(String data) {
//拿到H5端回調回來的參數
}
});
//H5端的代碼
bridge.registerHandler("functionInJs", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("data from Java: = " + data);
var responseData = "Javascript Says Right back aka!";
//將參數回調給native端
responseCallback(responseData);
});
java調用callHandler()的方法,必須要在H5中registerHandler(),而且方法名一樣才能匹配到,
所以執行上可以看成 java—>js—>java。
1.2,js調用java
1,js發送消息給native端,并有回調
因為沒有指定要處理的方法,則由java端的DefaultHandler攔截處理
//js代碼
window.WebViewJavascriptBridge.send(
data
, function(responseData) {
//拿到H5端回調回來的參數
document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
}
);
//java代碼
//設置默認回調給JS的方法
webView.setDefaultHandler(new DefaultHandler());
public class DefaultHandler implements BridgeHandler {
String TAG = "DefaultHandler";
@Override
public void handler(String data, CallBackFunction function) {
if (function != null) {
//將參數回調到H5端
function.onCallBack("DefaultHandler response data"+ data);
}
}
}
js調用window.WebViewJavascriptBridge.send()的方法,會被java的DefaultHandler攔截
所以執行上可以看成 js—>java—>js。
2,js調用java指定方法,并有回調
//js代碼
window.WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': '中文測試'}
, function(responseData) {
//拿到H5端回調回來的參數
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
//java代碼
webView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
ToastUtil.showShort(ActivityDemo7.this,"hadler = submitFromWeb, data from web = " + data);
//將參數回調到H5端
function.onCallBack("submitFromWeb exe, response data 中文 from Java");
}
});
2.執行流程分析
2.1,java調用js指定方法,并有回調。
在java代碼中,首先找到方法的入口,callHandler()。
webView.callHandler("functionInJs", params, new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
進入callHandler()方法,調用了doSend()方法。
public void callHandler(String handlerName, String data, CallBackFunction callBack) {
doSend(handlerName, data, callBack);
}
看doSend()代碼
/**
* native主動調用js代碼
* @param handlerName js中的對應方法的名稱
* @param data 要傳遞的參數
* @param responseCallback js代碼執行后回調到這里的接口
*/
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()));
// doSend()中callbackStr:JAVA_CB_3_998
// doSend()中callbackStr:JAVA_CB_4_1022
Log.i("BridgeWebView","doSend()中callbackStr:"+callbackStr);
responseCallbacks.put(callbackStr, responseCallback);
m.setCallbackId(callbackStr);
}
if (!TextUtils.isEmpty(handlerName)) {
m.setHandlerName(handlerName);
}
queueMessage(m);
}
這里組裝Message對象來保存調用方法的信息,假如存入了回調函數responseCallback ,則生成一個callbackStr 保存到本地的responseCallbacks中,繼續進入queueMessage()方法
private void queueMessage(Message m) {
//假如native調用js代碼,則startupMessage為null
if (startupMessage != null) {
startupMessage.add(m);
} else {
dispatchMessage(m);
}
}
假如native調用js代碼,則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);
// dispatchMessage()中javascriptCommand:javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"data\":\"hello\"}');
// dispatchMessage()中javascriptCommand:javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"callbackId\":\"JAVA_CB_1_877\",\"data\":\"{\\\"location\\\":{\\\"address\\\":\\\"SDU\\\"},\\\"name\\\":\\\"大頭鬼\\\"}\",\"handlerName\":\"functionInJs\"}');
Log.i("BridgeWebView","dispatchMessage()中javascriptCommand:"+javascriptCommand);
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
this.loadUrl(javascriptCommand);
}
}
這段代碼就是native調用js代碼的關鍵,方法中先從message對象中解析json數據將信息解析,拼接出類似
javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"callbackId\":\"JAVA_CB_1_877\",\"data\":\"{\\\"location\\\":{\\\"address\\\":\\\"SDU\\\"},\\\"name\\\":\\\"大頭鬼\\\"}\",\"handlerName\":\"functionInJs\"}
的代碼,再通過
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
this.loadUrl(javascriptCommand);
}
在主線程中webView.loadUrl(),所以其實java調用js的核心還是webView.loadUrl()這個方法,但是JsBridge庫對其進行了封裝,這樣java就不能調用js中的任意代碼(本來webView.loadUrl()這個方法只要傳入拼接對的js代碼,就能調用該js代碼)。
追蹤到js的WebViewJavascriptBridge._handleMessageFromNative代碼
function _handleMessageFromNative(messageJSON) {
console.log(messageJSON);
if (receiveMessageQueue && receiveMessageQueue.length > 0) {
receiveMessageQueue.push(messageJSON);
} else {
_dispatchMessageFromNative(messageJSON);
}
}
這里還沒搞懂receiveMessageQueue 的作用是什么,只知道調用了_dispatchMessageFromNative()方法,所以繼續進入方法
//按照messageJson進行事件的調用,假如有callback,則調用完之后callback
function _dispatchMessageFromNative(messageJSON) {
setTimeout(function() {
//解析出callbackId(假如存在回調)和參數,
var message = JSON.parse(messageJSON);
var responseCallback;
//java call finished, now need to call js callback function
//假如存在responseId,則回調完成時去除callId
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) {
//js回調給java
_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);
}
}
}
});
}
這段代碼中,因為是java主動調用js,所以message.responseId為null,直接走else,先組裝回調函數responseCallBack()
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
//js回調給java
_doSend({
responseId: callbackResponseId,
responseData: responseData
});
};
}
接著根據handler = messageHandlers[message.handlerName];
拿到handler
//查找指定handler
try {
handler(message.data, responseCallback);
} catch (exception) {
if (typeof console != 'undefined') {
console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
}
}
最后就是調用方法,追蹤到注冊的方法
connectWebViewJavascriptBridge(function(bridge) {
......
//為js注冊Handler,
bridge.registerHandler("functionInJs", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("data from Java: = " + data);
var responseData = "Javascript Says Right back aka!";
responseCallback(responseData);
});
})
最后調用了responseCallback(responseData),responseCallback()在上面已經組裝成功,就是調用了_doSend()這個方法
//sendMessage add message, 觸發native處理 sendMessage
//將數據返回到native,回調到native時就是調用這個方法
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;
}
這里涉及到一些前端的知識,利用iframe.src的變化,觸發了BridgeWebViewClient的shouldOverrideUrlLoading將數據返回到native端,這樣就實現了js回調到native了。
所以總體上流程是:
3,js調用java指定方法,并有回調
找到js代碼的入口,window.WebViewJavascriptBridge.callHandler(...)
//function的responseData是回調的值
//js調用特定的java方法
window.WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': '中文測試'}
, function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
進入callHandler()方法,也是調用了_doSend()方法,將數據傳遞到native
function callHandler(handlerName, data, responseCallback) {
_doSend({
handlerName: handlerName,
data: data
}, responseCallback);
}
與上面js調用java流程中的_doSend()一樣,通過改變iframe.src觸發native的shouldOverrideUrlLoading()方法
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
url = URLDecoder.decode(url, "UTF-8");
Log.i("BridgeWebViewClient","查看觸發的schema:"+url);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
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);
}
}
接著走的是webView.flushMessageQueue()這個方法
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<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();
// 是否是response
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
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);
}
}
}
}
});
}
}
這個方法是在BridgeWebView中定義的,做的是在主線程中調用loadUrl(javascript:WebViewJavascriptBridge._fetchQueue(), new CallBackFunction());
這個方法。
所以,追蹤到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傳遞的參數拼接入Url中,傳遞到native,而相應的shouldOverrideUrlLoading 中調用handlerReturnData()這個方法
/**
* 處理返回的數據
*
* @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;
}
}
這里就是調用native本地方法
if (f != null) {
f.onCallBack(data);
}
所以總體上流程是
留意整個流程,js的第一次_doSend()只不過是通知native調用javascript:WebViewJavascriptBridge._fetchQueue();來取消息,包括要調用的方法,參數,回調的id。所以其實真正調用native方法的是js層的_fetchQueue()。