關(guān)于WebViewJavascriptBridge的功能不多做介紹,有興趣的小伙伴可以搜一下
使用示例
iOS端使用如下
let bridge = WKWebViewJavascriptBridge(for: webView)
//原生調(diào)用js
bridge.callHandler("jsFunction", data: [:], responseCallback: { (obj) in
})
//native端注冊(cè)方法,以供js調(diào)用
bridge.registerHandler("nativeFunction") { (data, reponseCallback) in
}
實(shí)現(xiàn)原理:
js端有一個(gè)處理對(duì)象,window.WebViewJavascriptBridge對(duì)象,原生端也有一個(gè)類似的對(duì)象WebViewJavascriptBridgeBase,他們兩個(gè)在兩端,分別使用攔截url和注入js的方式,進(jìn)行通信,他們的主要工作內(nèi)容是
存儲(chǔ)自己端registed handler(實(shí)質(zhì)是個(gè)block數(shù)組)
存儲(chǔ)回調(diào)(實(shí)質(zhì)也是個(gè)block數(shù)組)
對(duì)需要通信數(shù)據(jù)進(jìn)行編碼和解碼
//js端存儲(chǔ)相關(guān)代碼
var messageHandlers = {}; //存儲(chǔ)registerHandler方法注冊(cè)過的block
var responseCallbacks = {}; //存儲(chǔ)callHandler中的responseCallback
//原生端存儲(chǔ)相關(guān)
@interface WebViewJavascriptBridgeBase : NSObject
@property (strong, nonatomic) NSMutableDictionary* responseCallbacks; //存儲(chǔ)callHandler中的responseCallback
@property (strong, nonatomic) NSMutableDictionary* messageHandlers; //存儲(chǔ)registerHandler方法注冊(cè)過的block
@end
初始化過程
原生端的初始化:原生端調(diào)用WebViewJavascriptBridge庫,生成需要的WebViewJavascriptBridge或者WKWebViewJavascriptBridge實(shí)例
js端的初始化:js端加載地址為“https://__bridge_loaded__”的新頁面,新頁面的加載事件觸發(fā)webview的代理方法
//WKWebView
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
//UIWebView
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
在代理方法中識(shí)別加載地址為“https://__bridge_loaded__”,調(diào)用WebViewJavascriptBridge庫中預(yù)先寫好的一段js代碼 — WebViewJavascriptBridge_JS,使用evaluateJavaScript執(zhí)行這段js代碼,在js端生成window.WebViewJavascriptBridge對(duì)象及一些需要的js方法。
這種交互方式,比較通用的叫法是 url sheme
原生調(diào)用js
原生調(diào)用js的callHandler方法,實(shí)際上是調(diào)用WebViewJavascriptBridgeBase中方法對(duì)參數(shù)進(jìn)行處理。
WebViewJavascriptBridgeBase會(huì)為此次調(diào)用的回調(diào)生成一個(gè)id,并在responseCallbacks中用ID為key存儲(chǔ)回調(diào)。然后把js方法名、參數(shù)、回調(diào)id 組織成字典,再json字符串化。
{
"data": ["ID": 67788],
"callbackId": "objc_cb_1",
"handlerName": "jsFunction"
}
然后用“WebViewJavascriptBridge._handleMessageFromObjC”把這個(gè)message字符串包起來。_handleMessageFromObjC的是初始化時(shí)通過WebViewJavascriptBridge_JS注入js中的一個(gè)js方法。接下來通過evaluateJavaScript執(zhí)行handleMessageFromObjC。
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
js端的bridge方法handleMessageFromObjC被調(diào)用后,先將json字符串解析成字典對(duì)象,然后根據(jù)字典中的handlerName值,在messageHandlers中尋找注冊(cè)過的對(duì)應(yīng)name的function,將message.data和js回調(diào)傳給該function,進(jìn)行調(diào)用。
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
注意一點(diǎn),上面的responseCallback是js自己的回調(diào),原生的回調(diào)還留在WebViewJavascriptBridge的responseCallbacks字典中存儲(chǔ)。js回調(diào)的功能是給原生發(fā)送回調(diào)消息。
js回調(diào)調(diào)用_doSend方法中把字典類型的message添加進(jìn)sendMessageQueue數(shù)組
[handlerName: xxxx, responseId: xxx, responseData: xxx] //iOS 字典
{handlerName: xxxx, responseId: xxx, responseData: xxx} //js 字典
然后加載一個(gè)地址為“https://__wvjb_queue_message__”的新頁面,原生的代理方法被調(diào)用
WebViewJavascriptBridge中的代理方法識(shí)別到特殊的加載地址,使用evaluateJavaScript調(diào)用“WebViewJavascriptBridge._fetchQueue();”,js端的_fetchQueue把sendMessageQueue數(shù)組中的message都格式化成json字符串。原生拿到messages的json字符串后先把字符串轉(zhuǎn)換成數(shù)組,然后遍歷數(shù)組處理每個(gè)message(通常只有一個(gè))。原生bridge發(fā)現(xiàn)message中有responseId,則用responseId調(diào)用對(duì)應(yīng)的responseCallback。如果沒有responseId,說明這不是 原生調(diào)js - js回調(diào)原生,而是js直接調(diào)用原生的方法,那就從messageHandlers中尋找對(duì)應(yīng)handlerName的block
在原生端初始化WebViewJavascriptBridge的時(shí)候,WebViewJavascriptBridge就把webview的代理指向了自己,所以這里能攔截到。如果webview的代理被指向了其他地方,那WebViewJavascriptBridge就不工作了。畢竟js端初始化就指望著這個(gè)代理方法攔截到請(qǐng)求,然后去給js注入window.WebViewJavascriptBridge對(duì)象。
js調(diào)用原生
前面原生調(diào)用js中說到了原生發(fā)消息給js,js怎么處理,原生的回調(diào)怎么處理。js調(diào)用原生是類似的邏輯。只是雙方發(fā)消息的方式不一樣,一個(gè)是通過evaluateJavaScript,一個(gè)是通過url sheme。這里不再多述。