在 Android 中 WebView 是與 JS 交互的一個橋梁, JsBrigde 的本質(zhì)也是通過 WebView 的方法調(diào)用的 JS ,JS 同樣可以調(diào) WebView蜀撑。
對于 Android 調(diào)用 JS 碼的方法有 2 種:
1.通過 WebView 的 loadUrl()
2.通過 WebView 的 evaluateJavascript()
對于 JS 調(diào)用 Android 代碼的方法有 3 種:
1.通過 WebView 的 addJavascriptInterface() 進行對象映射
2.通過 WebViewClient 的 shouldOverrideUrlLoading () 方法回調(diào)攔截 url
3.通過 WebChromeClient 的 onJsAlert()必盖、onJsConfirm()鄙皇、onJsPrompt() 方法回調(diào)攔截JS對話框 alert()、confirm()、prompt() 消息
在 JsBrigde 庫中 Native 的部分就是通過 webView.loadUrl() 來調(diào)用的 JS 代碼钙姊,而 JS 的部分則是通過刷新 ifream.src 屬性來通知 WebViewClient 的 shouldOverrideUrlLoading ()方法,把數(shù)據(jù)傳遞到 Native 的须妻。雙方使用 json 格式來傳遞數(shù)據(jù)仔蝌。
JsBrigde源碼分析
無論是 JS 調(diào) Native,還是 Native 調(diào) JS荒吏,兩邊要約定好 handlerName敛惊,調(diào)用方傳遞的 handlerName,接收方同樣需要一致的 handlerName绰更,來保證雙方能夠正常通信瞧挤。
window.WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': '中文測試'}
, function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
webView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
Log.e(TAG, "handler = submitFromWeb, data from web = " + data);
function.onCallBack("submitFromWeb exe, response data 中文 from Java");
}
});
Js -> Native -> Js
下面這張圖就是 Js -> Native -> Js 的一個過程,可以結(jié)合這張圖儡湾,來看下面的分析
先以 JS 調(diào) Native 為例特恬,在 H5 中想調(diào) Native 方法 ,執(zhí)行 WebViewJavascriptBridge.callHandler 方法,此方法有三個參數(shù)徐钠,分別是: 與 Native 約定好的 handlerName癌刽,要發(fā)送的數(shù)據(jù)data,及用來接收 Native 返回數(shù)據(jù)的 responseCallBack,在 callHandler 方法中就干了一件事調(diào)用 _doSend 方法
//JS call native method
window.WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': '中文測試'}
, function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
function callHandler(handlerName, data, responseCallback) {
_doSend({
handlerName: handlerName,
data: data
}, responseCallback);
}
在 _doSend 方法中尝丐,首先判斷了有沒有 responseCallback妒穴,如果有,創(chuàng)建一個callbackId 賦值于 Message 對象摊崭,創(chuàng)建 responseCallBack 數(shù)組存儲 responseCallback讼油,創(chuàng)建 MessageQueue 添加 Message,然后調(diào)用 Iframe.src 屬性呢簸,刷新這個屬性時矮台,會走到 Native 中的 BridgeWebViewClient.shouldOverrideUrlLoading 方法,此方法可以攔截 URL
//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;
}
在 BridgeWebViewClient 的 shouldOverrideUrlLoading 方法中可以拿到 JS 中 ifream.src 刷新后的 URL根时,根據(jù)URL 的前綴調(diào)用了 webView.flushMessageQueue();
@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)) { // 如果是返回數(shù)據(jù)
webView.handlerReturnData(url);
return true;
} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
webView.flushMessageQueue();
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
在 flushMessageQueue 方法中先不看 CallBackFunction 的實現(xiàn)瘦赫,在這里調(diào)用了 webView 的 loadUrl() 方法來調(diào) JS 的 _fetchQueue();
//Js 方法
String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();"
void flushMessageQueue() {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {
}
}
public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
this.loadUrl(jsUrl);
// 添加至 Map<String, CallBackFunction>
responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);
}
在這里再次調(diào)用了Iframe.src,把 _doSend 方法存儲的 Message 數(shù)組轉(zhuǎn)換成 json 字符串蛤迎,然后拼接成了 URL 回傳到 Native确虱,這時再次走到 BridgeWebViewClient.shouldOverrideUrlLoading 方法
// 提供給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
bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}
這次根據(jù)URL前綴會調(diào)用 handlerReturnData(url)方法,并把URL數(shù)據(jù)傳過去替裆。
@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)) { // 如果是返回數(shù)據(jù)
webView.handlerReturnData(url);
return true;
} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
webView.flushMessageQueue();
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
在這里解析了URL校辩,取出 functionName,CallBackFunction 及 data辆童,并把 data 塞給 CallBackFunction宜咒。
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;
}
}
現(xiàn)在來看 CallBackFunction 的實現(xiàn),這代碼有點長把鉴,不過這次調(diào)用只會走一部分代碼故黑,首先把 data 轉(zhuǎn)成了 list,并且遍歷這個 list 取出 Message 及 Message 中的 responseId,這個時候 responseId 應(yīng)該是不存在的场晶,因為這時候 js 傳遞過來的數(shù)據(jù)就沒有 responseId混埠,可以回到 _doSend 方法看一下,Message 對象只存儲了callbackId诗轻,所以會走到 else 里岔冀,在這里取 callbackId 判斷是否為空,如果不為空概耻,說明 JS 在調(diào)用 Native 方法后還需要 Native 這邊的 “反饋”,在 callHandler 是就說了第三個參數(shù) responseCallBack 是用來接受 Native 返回來數(shù)據(jù)的回調(diào)罐呼, 那么這時候?qū)崿F(xiàn)了 Native 外部 registerHandler 該方法的 CallBackFunction 接口鞠柄,把 data 數(shù)據(jù)封裝成 Message 對象,給 Message 對象設(shè)置了一個 responseId, 接著調(diào)用了 queueMessage(responseMsg); 如果為空就說明 JS 不需要 Native 的數(shù)據(jù)回調(diào)嫉柴,而CallBackFunction也是空實現(xiàn)厌杜,最終根據(jù) handlerName 取出來 Native 對應(yīng)注冊的方法,把數(shù)據(jù)傳給上層计螺,這樣上層的registerHandler方法就收到 JS 傳過來的 data 數(shù)據(jù)了夯尽。
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 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);
}
}
}
}
});
還沒完,JS 還沒收到 Native 的數(shù)據(jù)回調(diào)登馒,接著看 queueMessage(responseMsg) , 直接調(diào)了 dispatchMessage匙握,在 dispatchMessage 中,把 Message 轉(zhuǎn)換 Json 字符串陈轿,拼接了 JS指令 圈纺,通過 webview.loadUrl() 調(diào)用了 JS 方法 _handleMessageFromNative
private void queueMessage(Message m) {
if (startupMessage != null) {
startupMessage.add(m);
} else {
dispatchMessage(m);
}
}
String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');";
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("(?<=[^\\\\])(\")", "\\\\\"");
messageJson = messageJson.replaceAll("(?<=[^\\\\])(\')", "\\\\\'");
String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
// 必須要找主線程才會將數(shù)據(jù)傳遞出去 --- 劃重點
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
this.loadUrl(javascriptCommand);
}
}
_handleMessageFromNative 調(diào)用了 _dispatchMessageFromNative 方法,同樣這段代碼也很長麦射,仔細看的話蛾娶,這段 JS 代碼和 Native 的 CallBackFunction 里的代碼邏輯上很像,這里依然只會走一部分代碼潜秋,在這里也會先取Message對象里的 responseId, 很明顯Native 傳過來的 Message 對象中有 responseId蛔琅,所以在這里就回調(diào)給了上層,callHandler 中的 responseCallBack 回調(diào)就有值了峻呛。下面的 else 也不會執(zhí)行罗售。
//提供給native調(diào)用,receiveMessageQueue 在會在頁面加載完后賦值為null,所以
function _handleMessageFromNative(messageJSON) {
console.log(messageJSON);
if (receiveMessageQueue) {
receiveMessageQueue.push(messageJSON);
}
_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);
}
}
}
});
}
到此 JS 調(diào)用 Native 方法并且得到 Native 返回的數(shù)據(jù)回調(diào),整個流程就走了一遍钩述,反過來其實也是一樣的莽囤,這里就不再跟進了,可以參考下面這張圖自己屢一下切距。剛才說的兩段比較長的代碼中朽缎,其實兩邊調(diào)用都走了這兩段代碼,每次單項調(diào)用就會走方法中對應(yīng)的某一部分。