WebViewJavascriptBridge 實(shí)現(xiàn)分析

UIWebView 已被全面廢棄,故本文只分析 WKWebView 實(shí)現(xiàn)。源碼見(jiàn) WebViewJavascriptBridge

先來(lái)看下在 WKWebView 下,是怎么使用JSBridge 的。

    WKWebView* webView = [[NSClassFromString(@"WKWebView") alloc] initWithFrame:self.view.bounds];
    webView.navigationDelegate = self;
    [self.view addSubview:webView];
    [WebViewJavascriptBridge enableLogging];
    _bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
    [_bridge setWebViewDelegate:self];
    
    [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"testObjcCallback called: %@", data);
        responseCallback(@"Response from testObjcCallback");
    }];
    
    [_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];

所以,看來(lái) JSBridge 對(duì)象是 WebViewJavascriptBridge 實(shí)現(xiàn)的。

WebViewJavascriptBridge.h宏定義如下

#if defined __MAC_OS_X_VERSION_MAX_ALLOWED
    #define WVJB_PLATFORM_OSX
    #define WVJB_WEBVIEW_TYPE WebView
    #define WVJB_WEBVIEW_DELEGATE_TYPE NSObject<WebViewJavascriptBridgeBaseDelegate>
    #define WVJB_WEBVIEW_DELEGATE_INTERFACE NSObject<WebViewJavascriptBridgeBaseDelegate, WebPolicyDelegate>
#elif defined __IPHONE_OS_VERSION_MAX_ALLOWED
    #import <UIKit/UIWebView.h>
    #define WVJB_PLATFORM_IOS
    #define WVJB_WEBVIEW_TYPE UIWebView
    #define WVJB_WEBVIEW_DELEGATE_TYPE NSObject<UIWebViewDelegate>
    #define WVJB_WEBVIEW_DELEGATE_INTERFACE NSObject<UIWebViewDelegate, WebViewJavascriptBridgeBaseDelegate>
#endif

所以,在 iOS 上,其本質(zhì)是

NSObject<UIWebViewDelegate, WebViewJavascriptBridgeBaseDelegate>

WKWebView 也是這個(gè)類,也遵守 UIWebViewDelegate? 是否有點(diǎn)奇怪?

看其內(nèi)部實(shí)現(xiàn),就會(huì)發(fā)現(xiàn),當(dāng)系統(tǒng)支持 WebKit 時(shí),并且用戶傳的是 WKWebView 時(shí),實(shí)例化 WebViewJavascriptBridge 對(duì)象時(shí),就直接以 WKWebView 進(jìn)行了初始化。

+ (instancetype)bridge:(id)webView {
#if defined supportsWKWebView
    if ([webView isKindOfClass:[WKWebView class]]) {
        return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
    }
#endif
    if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) {
        WebViewJavascriptBridge* bridge = [[self alloc] init];
        [bridge _platformSpecificSetup:webView];
        return bridge;
    }
    [NSException raise:@"BadWebViewType" format:@"Unknown web view type."];
    return nil;
}

所以,接下來(lái),我們就去看 WKWebViewJavascriptBridge
實(shí)例化對(duì)象

+ (instancetype)bridgeForWebView:(WKWebView*)webView {
    WKWebViewJavascriptBridge* bridge = [[self alloc] init];
    [bridge _setupInstance:webView];
    [bridge reset];
    return bridge;
}
- (void) _setupInstance:(WKWebView*)webView {
    _webView = webView;
    _webView.navigationDelegate = self;
    _base = [[WebViewJavascriptBridgeBase alloc] init];
    _base.delegate = self;
}

上述代碼又實(shí)例化了一個(gè)很重要成員變量 WebViewJavascriptBridgeBase

整個(gè) JSBridege objc 代碼的實(shí)現(xiàn)就集中在了 WKWebViewJavascriptBridgeWebViewJavascriptBridgeBase,此外還有一個(gè) JS 代碼的注入文件 WebViewJavascriptBridge_js,整個(gè)庫(kù)的核心可以說(shuō)就是這幾個(gè)文件了。

WebViewJavascriptBridge 加載

jsbridge-load

可以看到,WebViewJavascriptBridge 加載過(guò)程主要如下:

  1. h5 網(wǎng)頁(yè)加載,內(nèi)部添加了一個(gè)不展示的 iframe,其 src 屬性設(shè)置為了 https://__bridge_loaded__
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'https://__bridge_loaded__';
document.documentElement.appendChild(WVJBIframe);
  1. iframe src 加載時(shí),其請(qǐng)求被 WKWebView WKNavigationDelegate 代理方法攔截
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 
  1. 內(nèi)部判斷為加載 WebViewJavascriptBridge 請(qǐng)求,執(zhí)行注入 js 代碼 WebViewJavascriptBridge_js,實(shí)現(xiàn) JSBridge,掛載在 window 上。
    window.WebViewJavascriptBridge = {
        registerHandler: registerHandler,
        callHandler: callHandler,
        disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
        _fetchQueue: _fetchQueue,
        _handleMessageFromObjC: _handleMessageFromObjC
    };

注入 js 同時(shí),又添加了一個(gè) iframe 元素,其 src 設(shè)置為了 https://__wvjb_queue_message__,注入的時(shí)候就會(huì)加載,然后被 WKNavigationDelegate 攔截。

如果 load web 之前,就調(diào)用了 callHandler,則會(huì)先存儲(chǔ)起來(lái),先執(zhí)行注入 WebViewJavascriptBridge_js,然后再去 sendData。

- (void)injectJavascriptFile {
    NSString *js = WebViewJavascriptBridge_js();
    [self _evaluateJavascript:js];
    if (self.startupMessageQueue) {
        NSArray* queue = self.startupMessageQueue;
        self.startupMessageQueue = nil;
        for (id queuedMessage in queue) {
            [self _dispatchMessage:queuedMessage];
        }
    }
}

native 調(diào)用 h5

jsbridge-native-invoke-h5.png

以 native 調(diào)用H5 testJavascriptHandler 方法為例, native 調(diào)用 h5 主要經(jīng)歷如下幾個(gè)步驟:

  1. h5 首先注冊(cè) testJavascriptHandler
  2. native 發(fā)送的數(shù)據(jù),被包裝成 json 字符串作為參數(shù),然后使用 WebViewJavascriptBridge._handleMessageFromObjC('%@'); 在 WKWebView 執(zhí)行 js 代碼。json 字符串包含:data callbackId handlerName 參數(shù)。同時(shí)將回調(diào)根據(jù)callbackId存儲(chǔ)起來(lái)。(callbackId 使用 objc_cb_ + 自增 id)
  3. js 方法 _dispatchMessageFromObjC 來(lái)處理數(shù)據(jù)。如果有 callbackId,則會(huì)使用 _doSend 方法,將callbackId 轉(zhuǎn)為 responseId ,加入 testJavascriptHandler 返回的 responseData , 然后將數(shù)據(jù)存儲(chǔ)到 sendMessageQueue 對(duì)象中,然后加載 messagingIframe src,src 鏈接為 https://__wvjb_queue_message__
  4. WKNavigationDelegate 代理攔截到 messagingIframe 重新加載 信息,執(zhí)行 js 代碼 WebViewJavascriptBridge._fetchQueue(); 獲取存在 sendMessageQueue 中數(shù)據(jù)。
  5. sendMessageQueue 中獲取到 responseId,根據(jù) responseId 取到存儲(chǔ)在2 中存儲(chǔ)在 responseCallbacks 的 callback handler,然后 native 調(diào)用 handler,傳入 js 的 responseData。

h5 調(diào)用 native

jsbridge-h5-invoke-native.png

以 h5 調(diào)用 native testObjcCallback handler 為例:

  1. native 需要先注冊(cè) testObjcCallback handler
  2. h5 調(diào)用 handler testObjcCallback
bridge.callHandler('testObjcCallback', { 'foo': 'bar' }, function (response) {
  log('JS got response', response)
})
  1. js _doSend() 方法,處理調(diào)用,將 handlerName data callbackId 包裝成JSON 字典對(duì)象, 存到 sendMessageQueue 中,并存儲(chǔ)回調(diào)(如有調(diào)用有回調(diào)),然后設(shè)置 messagingIframe,讓其重新加載https://__wvjb_queue_message__ 。其中 callbackId 生成規(guī)則 'cb_'+(uniqueId++)+'_'+new Date().getTime()

  2. WKNavigationDelegate 代理攔截 https://__wvjb_queue_message__,處理調(diào)用。首先通過(guò) WebViewJavascriptBridge._fetchQueue(); 獲取 js 數(shù)據(jù),即 sendMessageQueue 中數(shù)據(jù)。

  3. 根據(jù) sendMessageQueue 中數(shù)據(jù),是否有 callbackId。如果有 callbackId, 即在 native handler 中,傳入 responseData,并將 callbackId 轉(zhuǎn)為 responseId, 然后再次 WKWebView 執(zhí)行 js 代碼。

  4. h5 WebViewJavascriptBridge 中 通過(guò) _handleMessageFromObjC 方法處理調(diào)用。拿到 responseDataresponseId, 根據(jù) responseId,找到3中存儲(chǔ)的回調(diào),然后執(zhí)行。

最后編輯于
?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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