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)系作者
  • 序言:七十年代末寨辩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子歼冰,更是在濱河造成了極大的恐慌靡狞,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隔嫡,死亡現(xiàn)場(chǎng)離奇詭異甸怕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)腮恩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)梢杭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人秸滴,你說(shuō)我怎么就攤上這事武契。” “怎么了荡含?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵咒唆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我内颗,道長(zhǎng)钧排,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任均澳,我火速辦了婚禮恨溜,結(jié)果婚禮上符衔,老公的妹妹穿的比我還像新娘。我一直安慰自己糟袁,他們只是感情好判族,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著项戴,像睡著了一般形帮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上周叮,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天辩撑,我揣著相機(jī)與錄音,去河邊找鬼仿耽。 笑死合冀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的项贺。 我是一名探鬼主播君躺,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼开缎!你這毒婦竟也來(lái)了棕叫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奕删,失蹤者是張志新(化名)和其女友劉穎俺泣,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體急侥,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡砌滞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坏怪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贝润。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖铝宵,靈堂內(nèi)的尸體忽然破棺而出打掘,到底是詐尸還是另有隱情,我是刑警寧澤鹏秋,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布尊蚁,位于F島的核電站,受9級(jí)特大地震影響侣夷,放射性物質(zhì)發(fā)生泄漏横朋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一百拓、第九天 我趴在偏房一處隱蔽的房頂上張望琴锭。 院中可真熱鬧晰甚,春花似錦、人聲如沸决帖。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)地回。三九已至扁远,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刻像,已是汗流浹背畅买。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留细睡,地道東北人皮获。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纹冤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子购公,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容