JsBridge 是 Android 中 WebView 與 Javascript 互相調(diào)用的一個庫瑞躺,github 地址 為 https://github.com/lzyzsd/JsBridge退盯。
本文首先會介紹 JsBridge 中 Javascript 與 Java 通信的原理,其后會使用汰瘫,最后具體介紹 JsBridge 項目的實現(xiàn)原理
一:JsBridge 中 Javascript 與 Java 通信的原理
1.webViewClient.shouldOverrideUrlLoading()
在加載一個新的 url 連接時的一個回調(diào)。目的是給宿主應(yīng)用一個機會是否攔截該新的 url 連接請求渴语。 如果返回 true茵瀑,表示應(yīng)用處理了該 url,返回false 則意味著將 url 請求交給當(dāng)前的 WebView 來處理惜犀。
2. WebView 獲取 Javascript 返回的內(nèi)容
JsBridge 通過重載該方法铛碑,判斷 WebView 加載的 url 中是否包含有與 JS 約定的 schema。Java 端正是通過解析 schema 拿到
Javascript 返回給我們的內(nèi)容
3. Javascript 獲取 WebView 的內(nèi)容
webView.loadUr() 方法可以執(zhí)行 JS代碼虽界。故我們可以通過該方式執(zhí)行 JS 聲明的方法汽烦。傳入我們需要傳遞的數(shù)據(jù)。
比如:‘javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');’
4. Javascript 刷新 webView 頁面
html 中 iframe 元素
iframe 元素會創(chuàng)建包含另外一個文檔的內(nèi)聯(lián)框架
通過設(shè)置 iframe.src = url 來刷新頁面莉御。其中 src 屬性可設(shè)置或返回被載入 iframe 中的文檔的 url撇吞。當(dāng) web 端想要傳遞數(shù)據(jù)給Java 端時俗冻,可以將內(nèi)容序列化包裝成 url 。
二:JsBridge 的使用
1. 添加 gradle 依賴
repositories {
// ...
maven { url "https://jitpack.io" }
}
dependencies {
compile 'com.github.lzyzsd:jsbridge:1.0.4'
}
2. java 提供 Javascript 調(diào)用
webview 注冊 “submitFromWeb” 方法名的 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");
}
});
Javascript 調(diào)用 submitFromWeb 對應(yīng)的 handler
WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': str1}
, function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
3. Javascript 提供 java 調(diào)用
Javascript 注冊 Handler
WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("data from Java: = " + data);
var responseData = "Javascript Says Right back aka!";
responseCallback(responseData);
});
java 端調(diào)用 Handler
webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
三:原理分析
tips:下面內(nèi)容是根據(jù) JsBridge 的具體源碼流程來進行分析的牍颈,需要先閱讀源碼迄薄,否則會云里霧里不知道講啥。
準(zhǔn)備工作
WebView 初始化
WebViewClient中的 onPageFinished() 回調(diào)觸發(fā)時煮岁,webView 加載本地的WebViewJavascriptBridge.js 文件噪奄。WebViewJavascriptBridge 初始化
在該 JS 文件中,首先會創(chuàng)建倆個 iframe人乓,然后 docuemnt.createEvent("Events") 創(chuàng)建并發(fā)送一個 WebViewJavascriptBridgeReady 的事件,表明 WebViewJavascriptBridge.js 文件已加載完畢都毒。demo.html 所做的工作
該頁面加載時會進行連接 WebViewJavascriptBridge 的任務(wù)色罚。該任務(wù)下會利用 document.addEventListener 監(jiān)聽 WebViewJavascriptBridgeReady 的事件,當(dāng)該事件到來時账劲,取出其中的 webViewJavascriptBridge 對象戳护,調(diào)用 bridge.init() 與 bridge.registerHandler方法。bridge.init
該方法是設(shè)置默認(rèn)的 messageHandler (消息處理函數(shù))瀑焦,將其保存在
webViewJavascriptBridge._messageHandler 中腌且。bridge.registerHandler 方法則是 Web 端設(shè)置可供Java 端調(diào)用指定方法的處理函數(shù)
java 調(diào)用 JavaScript 流程.
上面是 Java 調(diào)用 Javascript 的處理方法的流程圖,接下來根據(jù)序號一一介紹
流程1
BridgeWebView 中使用 callHandler(String handlername, String data, CallBackFunction callback).方法調(diào)用 JS 中的處理函數(shù)榛瓮。 其中 callback 函數(shù)會返回 JS 調(diào)用的結(jié)果铺董。
1.1 doSend 處理
callHandller 實現(xiàn)中調(diào)用 doSend(handlerName, data, callback)函數(shù),函數(shù)中將方法參數(shù)包裝成 Message 對象禀晓,設(shè)置調(diào)用 JS 方法名 message.handleName屬性精续;設(shè)置傳遞數(shù)據(jù) message.data。
為了在 JS 內(nèi)容返回時粹懒,準(zhǔn)確找到回調(diào)函數(shù)重付,這里為回調(diào)函數(shù)分配一個回調(diào) id 標(biāo)識該回調(diào)函數(shù)。 回調(diào) id 的組成由 “JAVA_CALLBACK” + uniqueId +當(dāng)前時間戳凫乖。同時使用
HashMap 將 回調(diào) id 與 回調(diào)函數(shù)聯(lián)系起來确垫。
1.2 queueMessage(Message m)
queueMessage 中又調(diào)用了 dispatchMessage(m)
1.3 dispatchmessage(Message m)
將 Message 對象 json 格式化,然后拼接 url 帽芽,使用 webView.loadUrl 執(zhí)行 JS 代碼删掀。這種的 url 格式為 javascript:WebViewJavascriptBridge._handleMessageFromNative('%s')其中 %s 為 message json 格式化后的數(shù)據(jù)。
流程2
2.1 _handleMessageFromNative(messageJSON);
使用 console.log(messageJSON); 輸出 logcat
接著調(diào)用 _dispatchMessageFromNative(messageJSON)
2.2 _dispatchMessageFromNative(messageJSON)
解析 messageJSON 反序列化為 Message 對象嚣镜,
此時 Message 對象的 callbackId 不為空爬迟,構(gòu)造 responseCallback。查看 messageHandlers 數(shù)組中是否有 message.handlerName對象的處理方法菊匿。有的話即調(diào)用對象函數(shù)付呕。當(dāng)返回結(jié)果為 WebView時计福,調(diào)用該 responseCallback,將結(jié)果返回給 JAVA 端徽职。 該回調(diào)中調(diào)用
_doSend({responseId : callbackResponseId, responseData: responseData}) 函數(shù)象颖。
流程3
3.1 _doSend(message, responseCallback)
這里將發(fā)送的消息保存到 sendMessageQueue.push(message)
messagingIFrame.src = YY://QUEUE_MESSAGE/
iframe.src 方法會刷新當(dāng)前網(wǎng)頁
流程4
4.1 WebViewClient.shouldOverrideUrlLoading(WebView view)
這時候該方法會回調(diào),執(zhí)行到 webView.flushMessageQueue();
流程5
5.1 webView.flushMessageQueue()
執(zhí)行 loadUrl('javascript:WebViewJavascriptBridge._fetchQueue();', CallBackFunction);
5.2 loadUrl
獲取url 中要調(diào)用 JS 端的方法名姆钉,將方法名與回調(diào)函數(shù)保存到 responseCallback
流程6
_fecthQueue()
獲取 JS 端調(diào)用 JAVA 端的消息隊列说订。
流程7
JSON 格式化之后,調(diào)用 bizMEssageIframe.src = YY://return/_fetchQueue/encodeURIConponent(messageQueueString)
該消息隊列的內(nèi)容都是 JS 端要返回給 WebView的消息
流程8
webView.handlerReturnData(url)
同樣 shouldOverrideUrlLoading 方法會回調(diào)會攔截潮瓶,根據(jù)“YY://return”得知這是 JS 端返回的內(nèi)容陶冷,即調(diào)用 webView.handlerReturnData(url)
該函數(shù)內(nèi)首先獲取函數(shù)名 _fetchQueue,獲取之前保存在 responseCallbacks 中的 回調(diào)處理函數(shù)毯辅。調(diào)用函數(shù)埂伦,將 JS 端調(diào)用JAVA 端的消息隊列傳遞過去。
流程9
fetch_queue 的回調(diào)函數(shù)為
獲取消息隊列思恐,遍歷Message沾谜。 其中 messages 中 包含之前 JAVA 端調(diào)用 JS 端,JS處理之后的返回給 JAVA端的回調(diào)函數(shù)胀莹,故其中的 responseId 不為空基跑。 由于 responseId 與 Java 端調(diào)用JS 時 設(shè)置的
CallbackId 是一一對應(yīng)的。 取出其JAVA 設(shè)置的 回調(diào)函數(shù)描焰,調(diào)用該函數(shù)媳否,將 responseData 返回,至此 JAVA 端 調(diào)用 JS 端流程結(jié)束栈顷。