WebViewJavascriptBridge實(shí)現(xiàn)原理

關(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。這里不再多述。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容