<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JSBridge原理解析—以WebviewJavascriptBridge實(shí)現(xiàn)方式為例

          共 17185字,需瀏覽 35分鐘

           ·

          2021-06-26 06:06

          一、什么是 JSBridge?

          JSBridge 是一種 webview 側(cè)和 native 側(cè)進(jìn)行通信的手段,webview 可以通過 jsb 調(diào)用 native 的能力,native 也可以通過 jsb 在 webview 上執(zhí)行一些邏輯。

          二、JSB 的實(shí)現(xiàn)方式

          在比較流行的 JSBridge 中,主要是通過攔截 URL 請(qǐng)求來達(dá)到 native 端和 webview 端相互通信的效果的。

          這里我們以比較火的 WebviewJavascriptBridge 為例,來解析一下它的實(shí)現(xiàn)方式。

          源碼地址:https://github.com/marcuswestin/WebViewJavascriptBridge

          2-1、在 native 端和 webview 端注冊(cè) Bridge

          注冊(cè)的時(shí)候,需要在 webview 側(cè)和 native 側(cè)分別注冊(cè) bridge,其實(shí)就是用一個(gè)對(duì)象把所有函數(shù)儲(chǔ)存起來。

          function registerHandler(handlerName, handler{
              messageHandlers[handlerName] = handler;
          }
          - (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
              _base.messageHandlers[handlerName] = [handler copy];
          }

          2-2、在 webview 里面注入初始化代碼

          function setupWebViewJavascriptBridge(callback{
                 if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
                 if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
                 window.WVJBCallbacks = [callback];
                 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)
          }

          這段代碼主要做了以下幾件事:

          (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

          2-3、在 native 端監(jiān)聽 URL 請(qǐng)求

          iOS 中有兩種 webview,一種是 UIWebview,另一種是 WKWebview,這里以 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 請(qǐng)求并拿到 url

          (2)首先判斷isWebViewJavascriptBridgeURL,判斷這個(gè) url 是不是 webview 的 iframe 觸發(fā)的,具體可以通過 host 去判斷。

          (3)繼續(xù)判斷,如果是isBridgeLoadedURL,那么會(huì)執(zhí)行injectJavascriptFile方法,會(huì)向 webview 中再次注入一些邏輯,其中最重要的邏輯就是,在 window 對(duì)象上掛載一些全局變量和WebViewJavascriptBridge屬性,具體值如下:

          window.WebViewJavascriptBridge = {
                  registerHandler: registerHandler,
                  callHandler: callHandler,
                  disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
                  _fetchQueue: _fetchQueue,
                  _handleMessageFromObjC: _handleMessageFromObjC
          };

          var sendMessageQueue = [];
          var messageHandlers = {};

          var responseCallbacks = {};
          var uniqueId = 1;

          (4)繼續(xù)判斷,如果是 isQueueMessageURL,那么這就是個(gè)處理消息的回調(diào),需要執(zhí)行一些消息處理的方法(第四步會(huì)詳細(xì)講)

          2-4、webview 調(diào)用 native 能力

          當(dāng) native 和 webview 都注冊(cè)好了 Bridge 之后,雙方就可以互相調(diào)用了,這里先介紹 webview 調(diào)用 native 能力的過程。

          2-4-1、webview 側(cè) callHandler

          當(dāng) webview 調(diào)用 native 時(shí),會(huì)調(diào)用 callHandler 方法,這個(gè)方法具體邏輯如下:

          bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData{
                 console.log("JS received response:", responseData)
          })

          function callHandler(handlerName, data, responseCallback{
                  if (arguments.length == 2 && typeof data == 'function') {
                          responseCallback = data;
                          data = null;
                   }
                  _doSend({ handlerName:handlerName, data:data }, responseCallback);
          }

          function _doSend(message, responseCallback{
                  if (responseCallback) {
                         var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
                         responseCallbacks[callbackId] = responseCallback;
                         message['callbackId'] = callbackId;
                   }
                   sendMessageQueue.push(message);
                   messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
          }

          實(shí)際上就是先生成一個(gè) message,然后 push 到 sendMessageQueue 里,然后更改 iframe 的 src。

          2-4-2、native 側(cè) flushMessageQueue

          然后,當(dāng) native 端檢測(cè)到 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。

          2-4-3、webview 側(cè) handleMessageFromObjC

          function _handleMessageFromObjC(messageJSON{
              _dispatchMessageFromObjC(messageJSON);
          }

          function _dispatchMessageFromObjC(messageJSON{
                 if (dispatchMessagesWithTimeoutSafety) {
                       setTimeout(_doDispatchMessageFromObjC);
           } else {
                       _doDispatchMessageFromObjC();
           }

           function _doDispatchMessageFromObjC({
                  var message = JSON.parse(messageJSON);
                  var messageHandler;
                  var responseCallback;
                  if (message.responseId) {
                          responseCallback = responseCallbacks[message.responseId];
                          if (!responseCallback) {
                                    return;
                          }
                          responseCallback(message.responseData);
                          delete responseCallbacks[message.responseId];
                   } else {
                         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);
                          }
                    }
               }
          }

          如果從 native 獲取到的 message 中有 responseId,說明這個(gè) message 是 JS 調(diào) Native 之后回調(diào)接收的 message,所以從一開始 sendData 中添加的 responseCallbacks 中根據(jù) responseId(一開始存的時(shí)候是用的 callbackId,兩個(gè)值是相同的)取出這個(gè)回調(diào)函數(shù)并執(zhí)行,這樣就完成了一次 JS 調(diào)用 Native 的流程。

          2-4-4、過程總結(jié)

          過程如下圖

          1、native 端注冊(cè) jsb

          2、webview 側(cè)創(chuàng)建 iframe,設(shè)置 src 為__bridge_load__

          3、native 端捕獲請(qǐng)求,注入 jsb 初始化代碼,在 window 上掛載相關(guān)對(duì)象和方法

          4、webview 側(cè)調(diào)用callHandler方法,并在responseCallback上添加callbackId: responseCallback,并修改 iframe 的 src,觸發(fā)捕獲

          5、native 收到 message,生成一個(gè)responseCallback,并執(zhí)行 native 側(cè)注冊(cè)好的方法

          6、native 執(zhí)行完畢后,通過 webview 執(zhí)行_handleMessageFromObjC方法,取出 callback 函數(shù),并執(zhí)行

          2-5、native 調(diào)用 webview 能力

          native 調(diào)用 webview 注冊(cè)的 jsb 的邏輯是相似的,不過就不是通過觸發(fā) iframe 的 src 觸發(fā)執(zhí)行的了,因?yàn)?Native 可以自己主動(dòng)調(diào)用 JS 側(cè)的方法。其具體過程如下圖:

          1、native 側(cè)調(diào)用callHandler方法,并在responseCallback上添加callbackId: responseCallback

          2、native 側(cè)主動(dòng)調(diào)用_handleMessageFromObjC方法,在 webview 中執(zhí)行對(duì)應(yīng)的邏輯

          3、webview 側(cè)執(zhí)行結(jié)束后,生成帶有responseId的 message,添加到sendMessageQueue中,并修改 iframe 的 src 為__wvjb_queue_message__

          4、native 端攔截到 url 變化,調(diào)用 webview 的邏輯獲取到 message,拿到responseId,并執(zhí)行對(duì)應(yīng)的 callback 函數(shù)

          點(diǎn)個(gè)『在看』支持下 
          瀏覽 53
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩中文字幕 | 中文字幕日韩免费 | 我要看国产一级黄片 | 曰本手机在线 | 嫩草成人 |