前言
JSBridgeYes主要是為了替代WebViewJavascriptBridge弛车,同時(shí)也有兼容原來的方法齐媒,所以對WebViewJavascriptBridge的源碼以及原理做了比較深入的了解,以下主要是針對源碼的解讀
一帅韧、介紹
WebviewJavascriptBridge是一個(gè)第三方的支持webview和native進(jìn)行通信的庫里初,通過JSBridge,webview可以調(diào)用native的能力,native也可以webview上執(zhí)行一些邏輯忽舟。
二双妨、基本構(gòu)成
對于整個(gè)框架來說,一共有三部門組成
- OC部分:包括oc處理暴露給js接口的類:WKWebViewJavascriptBridge(WebViewJavascriptBridge使用的是UIWebView叮阅,基本不用了)
- js部分:包括js處理暴露給oc接口的文件: ExampleApp.html
- bridge處理部分:這個(gè)部分對于oc和js都各有一個(gè)文件刁品,oc是類:WebViewJavascriptBridgeBase,js則是WebViewJavascriptBridge_JS
- oc和js部分的作用就是聲明給對方調(diào)用的方法浩姥,以及提供供自身使用可以調(diào)用對方的一個(gè)接口挑随,但是具體如何調(diào)用的js、如果調(diào)用的oc或者說如何注冊給js勒叠、如何注冊給oc使用的方法兜挨,這些邏輯都放在兩端的bridge部分進(jìn)行處理。
三眯分、WVJB實(shí)現(xiàn)
1. 初始化
// iOS 初始化
self.brige = [WebViewJavascriptBridge bridgeForWebView:_webView];
[self.brige registerHandler:@"imageSelectFunc" handler:^(id data, WVJBResponseCallback responseCallback) {
}];
在native端和webview端注冊Bridge,本質(zhì)就是用一個(gè)對象把所有函數(shù)儲(chǔ)存起來
// js
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
// oc
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
2.在webview里面注入初始化代碼
// Html
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback]; // 創(chuàng)建一個(gè) WVJBCallbacks 全局屬性數(shù)組拌汇,并將 callback 插入到數(shù)組中。
var WVJBIframe = document.createElement('iframe'); // 創(chuàng)建一個(gè) iframe 元素
WVJBIframe.style.display = 'none'; // 不顯示
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; // 設(shè)置 iframe 的 src 屬性
document.documentElement.appendChild(WVJBIframe); // 把 iframe 添加到當(dāng)前文導(dǎo)航上弊决。
setTimeout(function() {document.documentElement.removeChild(WVJBIframe) }, 0)
}
// 這里主要是注冊 OC 將要調(diào)用的 JS 方法
setupWebViewJavascriptBridge(function(bridge){
});
這段代碼主要做了以下幾件事:
(1)創(chuàng)建一個(gè)名為WVJBCallbacks的數(shù)組噪舀,將傳入的callback參數(shù)放到數(shù)組內(nèi)
(2)創(chuàng)建一個(gè)iframe魁淳,設(shè)置不可見,設(shè)置src為 https://bridge_loaded
(3)設(shè)置定時(shí)器移除這個(gè)iframe
3.在客戶端監(jiān)聽URL請求
// WKWebView為例
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
if (webView != _webView) { return; }
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationResponse:decisionHandler:)]) {
[strongDelegate webView:webView decidePolicyForNavigationResponse:navigationResponse decisionHandler:decisionHandler];
}
else {
decisionHandler(WKNavigationResponsePolicyAllow);
}
}
這段代碼主要做了以下幾件事:
(1)攔截了所有的URL請求并拿到url
(2)首先判斷isWebViewJavascriptBridgeURL与倡,判斷這個(gè)url是不是webview的iframe觸發(fā)的界逛,具體可以通過host去判斷。
(3)繼續(xù)判斷纺座,如果是isBridgeLoadedURL息拜,那么會(huì)執(zhí)行injectJavascriptFile方法,會(huì)向webview中再次注入一些邏輯比驻,其中最重要的邏輯就是该溯,在window對象上掛載一些全局變量和WebViewJavascriptBridge屬性
(4)繼續(xù)判斷,如果是isQueueMessageURL别惦,那么這就是個(gè)處理消息的回調(diào),需要執(zhí)行一些消息處理的方法
4. webview調(diào)用native
當(dāng)webview調(diào)用native時(shí)夫椭,會(huì)調(diào)用callHandler方法
// h5代碼有封裝 截取一部分
function invoke (fun, params, cb) {
setupWebViewJavascriptBridge(function (bridge) {
bridge.callHandler(fun, params, function (res) {
cb && cb(res)
console.log('invoke', res)
let logData = {
JsBridge: 'invoke',
BridgeName: fun,
BridgeParam: params,
Response: res
}
logger.info(logData)
})
})
}
實(shí)際上就是生成一個(gè)message掸掸,然后push到sendMessageQueue里,然后更改iframe的src蹭秋。
5. native側(cè)接受消息 flushMessageQueue
當(dāng)native端檢測到iframe src的變化時(shí)扰付,會(huì)走到isQueueMessageURL的判斷邏輯,然后執(zhí)行WKFlushMessageQueue函數(shù)仁讨,獲取到JS側(cè)的sendMessageQueue中的所有message
- (void)WKFlushMessageQueue {
[_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {
if (error != nil) {
NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
}
[_base flushMessageQueue:result];
}];
}
- (void)flushMessageQueue:(NSString *)messageQueueString{
if (messageQueueString == nil || messageQueueString.length == 0) {
NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
return;
}
id messages = [self _deserializeMessageJSON:messageQueueString];
for (WVJBMessage* message in messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
continue;
}
[self _log:@"RCVD" json:message];
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
handler(message[@"data"], responseCallback);
}
}
}
當(dāng)一個(gè)message結(jié)構(gòu)存在responseId的時(shí)候說明這個(gè)message是執(zhí)行bridge后傳回的羽莺。
取不到responseId說明是第一次調(diào)用bridge傳過來的,這個(gè)時(shí)候會(huì)生成一個(gè)返回給調(diào)用方的message洞豁,其reponseId是傳過來的message的callbackId盐固,當(dāng)native執(zhí)行responseCallback時(shí),會(huì)觸發(fā)_dispatchMessage方法執(zhí)行webview環(huán)境的的js邏輯丈挟,將生成的包含responseId的message返回給webview刁卜。
6. native側(cè)發(fā)送消息 sendData
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
NSMutableDictionary* message = [NSMutableDictionary dictionary];
if (data) {
message[@"data"] = data;
}
if (responseCallback) {
NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
self.responseCallbacks[callbackId] = [responseCallback copy];
message[@"callbackId"] = callbackId;
}
if (handlerName) {
message[@"handlerName"] = handlerName;
}
OC要調(diào)用javascript環(huán)境的方法,其實(shí)就是調(diào)用ExampleApp.html中的bridge.registerHandler注冊的方法曙咽。把所有信息存入一個(gè)名字為message的字典中蛔趴。里面拼裝好參數(shù)data、回調(diào)IDcallbackId例朱、消息名字handlerName,把OC消息序列化孝情、并且轉(zhuǎn)化為javascript環(huán)境的格式。然后在主線程中調(diào)用_evaluateJavascript洒嗤。
WebViewJavascriptBridge._handleMessageFromObjC('{\"callbackId\":\"objc_cb_1\",\"data\":{\"OC調(diào)用JS方法\":\"OC調(diào)用JS方法的參數(shù)\"},\"handlerName\":\"OC調(diào)用JS提供的方法\"}');
7.總結(jié)
結(jié)合上面的邏輯圖箫荡,原理其實(shí)很簡單
分別在OC環(huán)境和javascript環(huán)境都保存一個(gè)bridge對象,里面維持著requestId,callbackId,以及每個(gè)id對應(yīng)的具體實(shí)現(xiàn)烁竭。
OC通過javascript環(huán)境的window.WebViewJavascriptBridge對象來找到具體的方法菲茬,然后執(zhí)行。
javascript通過改變iframe的src來觸發(fā)webview的代理方法webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler從而實(shí)現(xiàn)把javascript消息發(fā)送給OC這個(gè)功能。