WebViewJavascriptBridge 源碼學習--了解其實現原理

WebViewJavascriptBridge 應該很多開發的同事都有接觸過,是一個挺好的原生與H5交互實現方案的三方開源庫。其實現的原理其實挺簡單的:
H5調用原生:是通過攔截加載的Url實現的。
原生調用H5:是通過執行Javascript字符串來實現的。
解決了上面兩個問題,就能實現H5與原生之間的方法調用,及可實現兩端的交互。
接下來就具體看一下代碼是怎么實現的。

先來看一下這個庫的文件


WebViewJavascriptBridge代碼結構.png

就這幾個問題,大致的功能已經在截圖標明,接下來就是具體的分析。

H5調用原生

H5調用原生方法的話,原生這邊的代碼實現就是下面幾行代碼

        //先搞個webview
        self.uiWebView = [[UIWebView alloc] initWithFrame:self.view.bounds];
        self.uiWebView.delegate = self;
        [self.view addSubview:self.uiWebView];

        //*1、初始化 WebViewJavascriptBridge 對象
        self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.uiWebView];

        //*2、設置代理 (應為webview的delegate在 WebViewJavascriptBridge 內部會被重新賦值,因為需要 其他代理來把webview的代理給轉發出去)
        [self.bridge setWebViewDelegate:self];

        //*3、注冊給H5調用的方法
        //方法名:daJiangYou,將來給H5調用的方法
        //入參:data,H5傳遞過來的參數
        //回調:responseCallback,原生方法執行完畢之后,通知H5,同時可傳遞相關數據給到H5,作為返回參數。
        [self.bridge registerHandler:@"daJiangYou" handler:^(id data, WVJBResponseCallback responseCallback) {
            //打印入參
            NSLog(@"%@", data);
            
            //搞事情
            //。。。。
            
            //結束,告訴H5
            responseCallback(@"OK");
        }];

        //*4、加載H5地址
        [self.uiWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.2.92:8081/index.html"]]];

主要就是分為上面的四個步驟,接下來就分析每個步驟干了什么玩意兒。

注意點:這里一定要注意,加載H5必須在用戶注冊方法之后,如果先加載H5,在H5中調用原生方法時,可能因為原生還未注冊而導致失敗。

1、初始化 WebViewJavascriptBridge 對象。

追蹤過去:會發現有三個方法會被調用。

//第一個方法
+ (instancetype)bridgeForWebView:(id)webView {
    return [self bridge:webView];
}

//第二個方法
+ (instancetype)bridge:(id)webView {
    //判斷WKWebView是否可用
#if defined supportsWKWebView
    //判斷是否為 WKWebView
    if ([webView isKindOfClass:[WKWebView class]]) {
        //執行 WKWebView 的初始化方法
        return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
    }
#endif
    if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) {
         //執行 WebView 的初始化方法
        WebViewJavascriptBridge* bridge = [[self alloc] init];
        //初始化的相關配置
        [bridge _platformSpecificSetup:webView];
        return bridge;
    }
    [NSException raise:@"BadWebViewType" format:@"Unknown web view type."];
    return nil;
}

//第三個方法
- (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView {
    _webView = webView;
    _webView.policyDelegate = self;
    _base = [[WebViewJavascriptBridgeBase alloc] init];
    _base.delegate = self;
}

同時存在對 WebViewJavascriptBridgeBase 的初始化:

- (id)init {
    if (self = [super init]) {
        self.messageHandlers = [NSMutableDictionary dictionary];  //原生注冊的提供給H5調用的方法的存儲
        self.startupMessageQueue = [NSMutableArray array]; //原生調用H5方法的消息隊列的存儲
        self.responseCallbacks = [NSMutableDictionary dictionary];  //原生調用H5方法的回調處理的存儲
        _uniqueId = 0;  //回調處理的唯一編號
    }
    return self;
}

簡單畫了個示意圖,希望大家可以看懂:


示意圖1.png
2、設置 WebViewJavascriptBridge 代理對象。

追蹤過去: 就下面這點代碼

- (void)setWebViewDelegate:(WVJB_WEBVIEW_DELEGATE_TYPE*)webViewDelegate {
    //負責對webview代理的轉發
    _webViewDelegate = webViewDelegate;
}
3、注冊給H5調用的方法

追蹤過去:將注冊的方法進行存儲。

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    //將H5提供的方法存儲在字典中
    //方法名:回調(具體的事件)
    //daJiangYou:打醬油方法的實現
    _base.messageHandlers[handlerName] = [handler copy];
}
示意圖2.png
4、加載H5地址

這就是加載一個普通的H5,沒啥好說的,直接跳過去。

5、H5調用原生方法的準備工作(為window注入用于完成交互的對象)

h5這邊需要一個JS的方法,通過這個方法來給 window 注入一個用于實現交互的對象。
代碼的具體實現如下:

export function setupWebViewJavascriptBridge(callback) {
    //如果window下有 WebViewJavascriptBridge 對象,就 回調這個對象,并返回
    if (window.WebViewJavascriptBridge) { return callback(window.WebViewJavascriptBridge);}
    //如果window下有 WVJBCallbacks 這個數組,就將 callback 存儲起來
    if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
    //沒有數組就初始化一個
    window.WVJBCallbacks = [callback];

    //弄一個看不見的元素,加載 https://__bridge_loaded__ 這個鏈接,用于為window注入 WebViewJavascriptBridge 對象
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'https://__bridge_loaded__';
    document.documentElement.appendChild(WVJBIframe);

    //之后就給干掉
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

在這里可以看到H5頁面加載了一個地址(https://_bridge_loaded_),這時我們就應該回到原始的頁面,看看在webview的代理里干了什么。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (webView != _webView) { return YES; }
    
    NSURL *url = [request URL];
    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
    //判斷是否為 WebViewJavascriptBridge 加載的用于交互的url
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) { //判斷是否為Bridge加載的url
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) { //判斷是否為發送消息的url
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            [_base flushMessageQueue:messageQueueString];
        } else { //未知規則
            [_base logUnkownMessage:url];
        }
        return NO;
    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    } else {
        return YES;
    }
}

可以發現,這個庫對URL的加載進行啦攔截處理。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
如果為其定義的特殊的地址,就進行相關操作。剛剛H5主動加載的H5地址就會滿足 [_base isBridgeLoadedURL:url] 這個條件,具體代碼就不看了,大家可以自己點進去瞅瞅。

因此就會執行下面的方法:

- (void)injectJavascriptFile {
    //獲取要注入的JS字符串
    NSString *js = WebViewJavascriptBridge_js();
    //執行JS代碼
    [self _evaluateJavascript:js];
    
    //這里為啥要這樣處理呢
    //因為存在這種情況:在原生調用H5方法的時候,H5還未初始化JavascriptBridge,也就是說H5那邊還沒有注冊相關方法,因此需要在初始化成功的時候將所有存儲的消息,發送給H5(但其實初始化成功 JavascriptBridge 時,方法同樣可能未注冊哦,寫這個有啥用呢)
    //如果 startupMessageQueue 數組里不為 nil
    if (self.startupMessageQueue) {
        //取出消息
        NSArray* queue = self.startupMessageQueue;
        //數組置空
        self.startupMessageQueue = nil;
        //所有存儲的消息要發給H5(這里屬于原生調用H5先跳過去)
        for (id queuedMessage in queue) {
            [self _dispatchMessage:queuedMessage];
        }
    }
}
- (void) _evaluateJavascript:(NSString *)javascriptCommand {
    //由代理對象來實現(uiwebview和wkwebview 各自執行自己的相關方法)
    [self.delegate _evaluateJavascript:javascriptCommand];
}

- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand {
    return [_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];
}

上面的代碼中有個執行JS代碼的方法,這個就是JavascriptBridge這個庫的核心所在了,我們來看看這個JS都干了什么。
首先我們可以發現它就是一個JS方法的字符串:

NSString * WebViewJavascriptBridge_js() {
    #define __wvjb_js_func__(x) #x
    
    // BEGIN preprocessorJSCode
    static NSString * preprocessorJSCode = @__wvjb_js_func__(
                                                             ;(function() {})();
    ); // END preprocessorJSCode

    #undef __wvjb_js_func__
    return preprocessorJSCode;
};

具體看看這個JS方法干了什么玩意兒,注釋寫的很清楚了,大家可以看看。

    //如果存在 WebViewJavascriptBridge 對象就直接返回
    if (window.WebViewJavascriptBridge) {
        return;
    }

    //錯誤處理
    if (!window.onerror) {
        window.onerror = function(msg, url, line) {
            console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
        }
    }
        
    //為window添加 WebViewJavascriptBridge 對象
    window.WebViewJavascriptBridge = {
        //對象包含的方法及屬性
        registerHandler: registerHandler,  //注冊方法給原生
        callHandler: callHandler,  //調用原生的方法
        disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,  //超時處理
        _fetchQueue: _fetchQueue,  //獲取待發送消息
        _handleMessageFromObjC: _handleMessageFromObjC  //處理原生發送的消息
    };

    var messagingIframe;  //發送消息的隱藏元素
    var sendMessageQueue = [];  //H5調用原生方法的存儲
    var messageHandlers = {};  //H5注冊的提供給原生調用的方法的存儲
    
    //規定的用于消息傳遞的特殊url
    var CUSTOM_PROTOCOL_SCHEME = 'https';
    var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
    
    var responseCallbacks = {};  //H5調用原生方法回調處理的存儲
    var uniqueId = 1; //回調處理的唯一編號
    var dispatchMessagesWithTimeoutSafety = true;  //是否開啟超時安全處理

   //先忽略各種各樣的方法

    //弄一個看不見的元素,加載特定url,用于調用原生提供的方法
    messagingIframe = document.createElement('iframe');
    messagingIframe.style.display = 'none';
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    document.documentElement.appendChild(messagingIframe);
        
    //為原生注冊 _disableJavascriptAlertBoxSafetyTimeout 方法,用來設置 dispatchMessagesWithTimeoutSafety 的值
    registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
    
    //初始化完成執行所有回調,將 WebViewJavascriptBridge 給到H5
    setTimeout(_callWVJBCallbacks, 0);
    function _callWVJBCallbacks() {
        var callbacks = window.WVJBCallbacks;
        delete window.WVJBCallbacks;
        for (var i=0; i<callbacks.length; i++) {
            callbacks[i](WebViewJavascriptBridge);
        }
    }

到這里所有的前期準備工作都完成啦,接下來看看H5怎么調用原生方法的吧。


示意圖3.png
6、H5調用原生方法

這時候只需要在適當的時機調用下面的方法就好啦:

        //點擊按鈕調用這個段代碼就可以嘍
        //初始化 JavascriptBridge,
        setupWebViewJavascriptBridge((bridge) => {
          //調用原生方法 daJiangYou
          bridge.callHandler('daJiangYou',{"message":"我要2斤醬油"}, function responseCallback(responseData) {
            //這邊原生回調信息(responseData),
            //處理自己的邏輯
          })
        });

WebViewJavascriptBridge 對象調用 callHandler 方法,我們去看看 callHandler 的都干嘛了,這里大家應該知道去哪里找 callHandler 方法的實現吧,就是原生的 WebViewJavascriptBridge_JS 文件中,因為H5獲取到的 bridge 對象就是通過這個文件產生的呢。

    function callHandler(handlerName, data, responseCallback) {
        //判斷參數個數
        //如果是兩個參數 且 第二個參數是一個方法
        if (arguments.length == 2 && typeof data == 'function') {
            //那么 data 就是回調的方法
            responseCallback = data;
            //第二個參數置空
            data = null;
        }
        //調用發送消息的方法
        _doSend({ handlerName:handlerName, data:data }, responseCallback);
    }
        
    // message:消息內容
    // responseCallback:回調處理
    function _doSend(message, responseCallback) {
        //如果存在處理回調的方法
        if (responseCallback) {
            //生成一個唯一的key
            var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
            //將回調方法存儲起來
            responseCallbacks[callbackId] = responseCallback;
            //消息中也要帶上這個key,以便原生方法完成后,通知H5
            message['callbackId'] = callbackId;
        }
        //待發送的消息存入數組
        sendMessageQueue.push(message);
        //加載一個H5地址
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

加載H5就會執行webview相關代理,并使 [_base isQueueMessageURL:url] 條件成立。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (webView != _webView) { return YES; }
    
    NSURL *url = [request URL];
    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
    //判斷是否為 WebViewJavascriptBridge 加載的用于交互的url
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) { //判斷是否為Bridge加載的url
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) { //判斷是否為發送消息的url
            //把H5中存儲的待發送消息取出來
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            //處理消息隊列
            [_base flushMessageQueue:messageQueueString];
        } else { //未知規則
            [_base logUnkownMessage:url];
        }
        return NO;
    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    } else {
        return YES;
    }
}

通過執行一個JS的方法,將H5那邊存儲的數據傳遞到原生這邊。

//獲取H5消息隊列,js方法
- (NSString *)webViewJavascriptFetchQueyCommand {
    return @"WebViewJavascriptBridge._fetchQueue();";
}

    //獲取待發送的消息隊列
    function _fetchQueue() {
        //把存儲待發送的消息數組,轉換為json串
        var messageQueueString = JSON.stringify(sendMessageQueue);
        //清空數組
        sendMessageQueue = [];
        //返回結果
        return messageQueueString;
    }

這個方法就是處理H5傳過來的所有消息。

/// 處理H5傳過來的消息
/// @param messageQueueString 消息數組對應的json串
- (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;
    }
    
    //json反序列化,獲取到待處理消息的數組
    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];
        
        //原生調用H5的相關邏輯這里可以先不管,直接看 else 語句
        //判斷是否為響應消息,(原生調用H5方法,H5處理完成時通知原生)
        NSString* responseId = message[@"responseId"];
        if (responseId) {
            //根據key找到對應的回調函數
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            //執行回調函數
            responseCallback(message[@"responseData"]);
            //將這個回調移除
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            //如果沒有上面的東西,則說明是H5調用原生的方法
            WVJBResponseCallback responseCallback = NULL;
            //取出當前消息的唯一標識的ID
            NSString* callbackId = message[@"callbackId"];
            if (callbackId) {
                //原生處理完相關邏輯,會調用 responseCallback ,來告訴H5
                responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    
                    //通過原生調用H5的方法,給H5發送一個響應消息
                    // responseId: 響應消息的唯一標識就是當前處理的消息的唯一標識
                    // responseData: 傳過去的參數
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
            }
            
            //根據方法名,找到對應的 handler(方法對應的實現)
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
            
            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            
            //執行當前方法
            //message[@"data"]: 參數
            //responseCallback: 回調
            handler(message[@"data"], responseCallback);
        }
    }
}

最終就會執行到我們最開始為H5注冊的方法的回調中。

        [self.bridge registerHandler:@"daJiangYou" handler:^(id data, WVJBResponseCallback responseCallback) {
            //打印入參
            NSLog(@"%@", data);
            
            //搞事情
            //。。。。
            
            //結束,告訴H5
            responseCallback(@"OK");
        }];

到此為止,H5就調用到了原生的方法嘍。


示意圖4.png
7、H5調用原生方法回調處理

H5調用原生方法之后,在原生方法處理完相關邏輯,我們需要告訴H5或者傳遞相關參數過去。

通過上面的方法我們可以看到,在block的最后,會調用 responseCallback(@"OK") 這個玩意兒,這時候就會執行上面我們看到的那個block。

responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    
                    //通過原生調用H5的方法,給H5發送一個響應消息
                    // responseId: 響應消息的唯一標識就是當前處理的消息的唯一標識
                    // responseData: 傳過去的參數
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };

這里就是調用H5的方法了,給H5發送了一個響應消息,看一下方法的具體實現嘍。

//給H5發送消息
- (void)_queueMessage:(WVJBMessage*)message {
    //H5為初始化 WebViewJavascriptBridge 就先存起來
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else {
        //發送消息
        [self _dispatchMessage:message];
    }
}

- (void)_dispatchMessage:(WVJBMessage*)message {
    //對象的序列化
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];
    //json串的格式化
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
    //調用JS方法 _handleMessageFromObjC,給H5傳遞數據
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}

最終跑到JS的方法里

    //處理原生傳過來的消息
    function _handleMessageFromObjC(messageJSON) {
        _dispatchMessageFromObjC(messageJSON);
    }

處理原生發過來的消息。

function _dispatchMessageFromObjC(messageJSON) {
        // 如果 dispatchMessagesWithTimeoutSafety 為true,則延遲一定時間處理(這邊庫的實現并沒有做延遲,也就是說沒啥用)
        if (dispatchMessagesWithTimeoutSafety) {
            setTimeout(_doDispatchMessageFromObjC);
        } else {
             _doDispatchMessageFromObjC();
        }
        
        function _doDispatchMessageFromObjC() {
            //字符串轉為對象
            var message = JSON.parse(messageJSON);
            
            var messageHandler;
            var responseCallback;
            
            //如果為響應消息
            if (message.responseId) {
                //取出 responseCallbacks 存儲的相關方法
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
                }
                //執行 responseCallback
                responseCallback(message.responseData);
                //并將這個東西從存儲的字典中移除
                delete responseCallbacks[message.responseId];
            } else {
                //不滿足上面條件則,為事件消息
                
                //事件消息唯一標識不為空
                if (message.callbackId) {
                    //H5處理完相關邏輯,會調用 responseCallback ,來告訴原生
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
                    };
                }
                
                //根據方法名,找到對應的 handler(方法對應的實現)
                var handler = messageHandlers[message.handlerName];
                if (!handler) {
                    console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                } else {
                    //執行handler
                    handler(message.data, responseCallback);
                }
            }
        }
    }

最終就會執行到我們調用原生方法的回調中

        setupWebViewJavascriptBridge((bridge) => {
          //調用原生方法 daJiangYou
          bridge.callHandler('daJiangYou',{"message":"我要2斤醬油"}, function responseCallback(responseData) {
            //這邊原生回調信息(responseData),
            //處理自己的邏輯
          })
        });
示意圖5.png

到這里一個完整的H5調用原生方法的實現就完全分析完了,希望對看到文章的小伙伴有所幫助。原生調用H5方法的原理和上面分析的過程一樣一樣的,這里就不多寫了,小伙伴們可以自己去分析一下哦。

最后上個大圖:


完整示意圖.png

總結一下

這個庫的實現H5與原生JS交互的原理就是兩段發送消息的過程。
其中有調用方法的消息,我們可以稱為
事件消息:
handlerName:事件名稱(方法名稱)
data:事件數據(方法參數)
callbackId:事件編號(每個消息對應事件的唯一編號)

還有對方調用方法,對方方法執行完畢后,對方發過來的執行結果的消息,我們可以稱為
響應消息
responseId:響應編號(響應的那個事件,這個就是對應事件的唯一編號)
responseData:響應數據(響應時傳過去的相關數據)

H5給原生發消息主要的過程:
1、將消息存儲在消息數組中。

function callHandler(handlerName, data, responseCallback);
function _doSend(message, responseCallback);

2、加載特定的URL。

messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;

3、原生攔截特定URL。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

4、調用相關JS方法,將消息數組轉化為JSON串返回給原生。

- (NSString *)webViewJavascriptFetchQueyCommand;
function _fetchQueue();

5、處理發送過來的消息。

- (void)flushMessageQueue:(NSString *)messageQueueString

原生給H5發消息主要的過程:
1、將消息轉換成格式化的字符串。

- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName;
- (void)_queueMessage:(WVJBMessage*)message;
- (void)_dispatchMessage:(WVJBMessage*)message;

2、通過webview執行JS方法,將格式化的字符串傳遞過去。

- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand;

3、處理發送過來的數據。

function _handleMessageFromObjC(messageJSON);
function _dispatchMessageFromObjC(messageJSON);

原生主要的存儲對象
原生注冊的提供給H5調用的方法的存儲
messageHandlers = [NSMutableDictionary dictionary];

原生調用H5方法的消息的存儲(主要用于在H5相關對象未初始化時,原生調用H5方法的存儲)
startupMessageQueue = [NSMutableArray array];

原生調用H5方法的回調處理的存儲
responseCallbacks = [NSMutableDictionary dictionary];

H5主要的存儲對象
H5調用原生方法消息的存儲
sendMessageQueue = [];

H5注冊的提供給原生調用的方法的存儲
messageHandlers = {};

H5調用原生方法回調處理的存儲
responseCallbacks = {};

OK,就寫這么多吧,完結!!!!

JS 交互的方法還有很多,有興趣的同學可以看看我這篇文章哦。
UIWebview、WKWebView中JS交互方法總結

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。