JS Bridge 通訊原理與實踐

前言

上一篇介紹了移動端開發的相關技術,這一篇主要是從 Hybrid 開發的 JS Bridge 通訊講起。javascript

顧名思義,JS Bridge 的意思就是橋,這是一個鏈接 JS 和 Native 的橋接,也是 Hybrid App 裏面的核心。通常分爲 JS 調用 Native 和 Native 主動調用 JS 兩種形式。前端

URL Scheme

URL Scheme 是一種特殊的 URL,通常用於在 Web 端喚醒 App,甚至跳轉到 App 的某個頁面,好比在某個手機網站上付款的時候,能夠直接拉起支付寶支付頁面。java

這裏有個小例子,你能夠在瀏覽器裏面直接輸入 weixin://,系統就會提示你是否要打開微信。輸入 mqq:// 就會幫你喚起手機 QQ。android

image.png-153.6kB

這裏有個經常使用 App URL Scheme 彙總:URL Schemes 收集整理ios

在手機裏面打開這個頁面後點擊這裏,就會提示你是否要打開微信。git

咱們常說的 Deeplink 通常也是基於 URL Scheme 來實現的。一個 URI 的組成結構以下:github

image.png-16.4kB

URI = scheme:[//authority]path[?query][#fragment]
// scheme = http
// authority = www.baidu.com
// path = /link
// query = url=xxxxx
authority = [userinfo@]host[:port]
複製代碼

除了 http/https 這兩個常見的協議,還能夠自定義協議。借用維基百科的一張圖:web

image.png-62.8kB

一般狀況下,App 安裝後會在手機系統上註冊一個 Scheme,好比 weixin:// 這種,因此咱們在手機瀏覽器裏面訪問這個 scheme 地址,系統就會喚起咱們的 App。npm

通常在 Android 裏面須要到 AndroidManifest.xml 文件中去註冊 Scheme:axios

<activity
    android:name=".login.dispatch.DispatchActivity"
    android:launchMode="singleTask"
    android:theme="@style/AppDispatchTheme">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="taobao" />
        <data android:host="xxx" />
        <data android:path="/goods" />
    </intent-filter>
</activity>
複製代碼

在 iOS 中須要在 Xcode 裏面註冊,有一些已是系統使用的不該該使用,好比 Maps、YouTube、Music。具體能夠參考蘋果開發者官網文檔:Defining a Custom URL Scheme for Your App

image.png-74.6kB

JS 調用 Native

在 iOS 裏面又須要區分 UIWebView 和 WKWebView 兩種 WebView:

image.png-47.2kB

WKWebView 是 iOS8 以後出現的,目的是取代笨重的 UIWebView,它佔用內存更少,大概是 UIWebView 的 1/3,支持更好的 HTML5 特性,性能更增強大。 但也有一些缺點,好比不支持緩存,須要本身注入 Cookie,發送 POST 請求的時候帶不了參數,攔截 POST 請求的時候沒法解析參數等等。

JS 調用 Native 通訊大體有三種方法:

  1. 攔截 Scheme
  2. 彈窗攔截
  3. 注入 JS 上下文

這三種方式整體上各有利弊,下面會一一介紹。

攔截 Scheme

仔細思考一下,若是是 JS 和 Java 之間傳遞數據,咱們該怎麼作呢? 對於前端開發來講,調 Ajax 請求接口是最多見的需求了。無論對方是 Java 仍是 Python,咱們均可以經過 http/https 接口來獲取數據。實際上這個流程和 JSONP 更加相似。

已知客戶端是能夠攔截請求的,那麼可不能夠在這個上面作文章呢?

若是咱們請求一個不存在的地址,上面帶了一些參數,經過參數告訴客戶端咱們須要調用的功能呢?

好比我要調用掃碼功能:

axios.get('http://xxxx?func=scan&callback_id=yyyy')
複製代碼

客戶端能夠攔截這個請求,去解析參數上面的 func 來判斷當前須要調起哪一個功能。客戶端調起掃碼功能以後,會獲取 WebView 上面的 callbacks 對象,根據 callback_id 回調它。

因此基於上面的例子,咱們能夠把域名和路徑當作通訊標識,參數裏面的 func 當作指令,callback_id 當作回調函數,其餘參數當作數據傳遞。對於不知足條件的 http 請求不該該攔截。

固然了,如今主流的方式是前面咱們看到的自定義 Scheme 協議,以這個爲通訊標識,域名和路徑當作指令。

這種方式的好處就是 iOS6 之前只支持這種方式,兼容性比較好。

JS 側

咱們有不少種方法能夠發起請求,目前使用最普遍的是 iframe 跳轉:

  1. 使用 a 標籤跳轉
<a href="taobao://">點擊我打開淘寶</a>
複製代碼
  1. 重定向
location.href = "taobao://"
複製代碼
  1. iframe 跳轉
const iframe = document.createElement("iframe");
iframe.src = "taobao://"
iframe.style.display = "none"
document.body.appendChild(iframe)
複製代碼

Android 側

在 Android 側能夠用 shouldOverrideUrlLoading 來攔截 url 請求。

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith("taobao")) {
        // 拿到調用路徑後解析調用的指令和參數,根據這些去調用 Native 方法
        return true;
    }
}
複製代碼

iOS 側

在 iOS 側須要區分 UIWebView 和 WKWebView 兩種方式。 在 UIWebView 中:

- (BOOL)shouldStartLoadWithRequest:(NSURLRequest *)request
                    navigationType:(BPWebViewNavigationType)navigationType
{

    if (xxx) {
        // 拿到調用路徑後解析調用的指令和參數,根據這些去調用 Native 方法
        return NO;
    }

    return [super shouldStartLoadWithRequest:request navigationType:navigationType];
}
複製代碼

在 WKWebView 中:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(nonnull WKNavigationAction *)navigationAction decisionHandler:(nonnull void (^)(WKNavigationActionPolicy))decisionHandler
{
    if(xxx) {
        // 拿到調用路徑後解析調用的指令和參數,根據這些去調用 Native 方法
        BLOCK_EXEC(decisionHandler, WKNavigationActionPolicyCancel);
    } else {
        BLOCK_EXEC(decisionHandler, WKNavigationActionPolicyAllow);
    }

    [self.webView.URLLoader webView:webView decidedPolicy:policy forNavigationAction:navigationAction];
}
複製代碼

目前不建議只使用攔截 URL Scheme 解析參數的形式,主要存在幾個問題。

  1. 連續續調用 location.href 會出現消息丟失,由於 WebView 限制了連續跳轉,會過濾掉後續的請求。
  2. URL 會有長度限制,一旦過長就會出現信息丟失

所以,相似 WebViewJavaScriptBridge 這類庫,就結合了注入 API 的形式一塊兒使用,這也是咱們這邊目前使用的方式,後面會介紹一下。

彈窗攔截

Android 實現

這種方式是利用彈窗會觸發 WebView 相應事件來攔截的。通常是在 setWebChromeClient 裏面的 onJsAlertonJsConfirmonJsPrompt 方法攔截並解析他們傳來的消息。

// 攔截 Prompt
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
       if (xxx) {
        // 解析 message 的值,調用對應方法
       }
       return super.onJsPrompt(view, url, message, defaultValue, result);
   }
// 攔截 Confirm
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
       return super.onJsConfirm(view, url, message, result);
   }
// 攔截 Alert
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
       return super.onJsAlert(view, url, message, result);
   }
複製代碼

iOS 實現

咱們以 WKWebView 爲例:

+ (void)webViewRunJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
    defaultText:(NSString *)defaultText
    completionHandler:(void (^)(NSString * _Nullable))completionHandler
{
    /** Triggered by JS:
    var person = prompt("Please enter your name", "Harry Potter");
    if (person == null || person == "") {
       txt = "User cancelled the prompt.";
    } else {
       txt = "Hello " + person + "! How are you today?";
    }
    */
    if (xxx) {
        BLOCK_EXEC(completionHandler, text);
    } else {
        BLOCK_EXEC(completionHandler, nil);
    }
 }

複製代碼

這種方式的缺點就是在 iOS 上面 UIWebView 不支持,可是 WKWebView 又有更好的 scriptMessageHandler,比較尷尬。

注入上下文

前面咱們有講過在 iOS 中內置了 JavaScriptCore 這個框架,能夠實現執行 JS 以及注入 Native 對象等功能。

這種方式不依賴攔截,主要是經過 WebView 向 JS 的上下文注入對象和方法,可讓 JS 直接調用原生。

PS:iOS 中的 Block 是 OC 對於閉包的實現,它本質上是個對象,定義 JS 裏面的函數。

iOS UIWebView

iOS 側代碼:

// 獲取 JS 上下文
JSContext *context = [webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 注入 Block
context[@"callHandler"] = ^(JSValue * data) {
    // 處理調用方法和參數
    // 調用 Native 功能
    // 回調 JS Callback
}
複製代碼

JS 代碼:

window.callHandler({
    type: "scan",
    data: "",
    callback: function(data) {
    }
});
複製代碼

這種方式的牛逼之處在於,JS 調用是同步的,能夠立馬拿到返回值。

咱們也再也不須要像攔截方式同樣,每次傳值都要把對象作 JSON.stringify,能夠直接傳 JSON 過去,也支持直接傳一個函數過去。

iOS WKWebView

WKWebView 裏面經過 addScriptMessageHandler 來注入對象到 JS 上下文,能夠在 WebView 銷燬的時候調用 removeScriptMessageHandler 來銷燬這個對象。 前端調用注入的原生方法以後,能夠經過 didReceiveScriptMessage 來接收前端傳過來的參數。

WKWebView *wkWebView = [[WKWebView alloc] init];
WKWebViewConfiguration *configuration = wkWebView.configuration;
WKUserContentController *userCC = configuration.userContentController;

// 注入對象
[userCC addScriptMessageHandler:self name:@"nativeObj"];
// 清除對象
[userCC removeScriptMessageHandler:self name:@"nativeObj"];

// 客戶端處理前端調用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    // 獲取前端傳來的參數
    NSDictionary *msgBody = message.body;
    // 若是是 nativeObj 就進行相應處理
    if (![message.name isEqualToString:@"nativeObj"]) {
        // 
        return;
    }
}
複製代碼

使用 addScriptMessageHandler 注入的對象實際上只有一個 postMessage 方法,沒法調用更多自定義方法。前端的調用方式以下:

window.webkit.messageHandlers.nativeObj.postMessage(data);
複製代碼

須要注意的是,這種方式要求 iOS8 及以上,並且返回不是同步的。和 UIWebView 同樣的是,也支持直接傳 JSON 對象,不須要 stringify。

Android addJavascriptInterface

安卓4.2以前注入 JS 通常是使用 addJavascriptInterface ,和前面的 addScriptMessageHandler 有一些相似,但又沒有它的限制。

public void addJavascriptInterface() {
        mWebView.addJavascriptInterface(new DatePickerJSBridge(), "DatePickerBridge");
    }
private class PickerJSBridge {
    public void _pick(...) {
    }
}
複製代碼

在 JS 裏面調用:

window.DatePickerBridge._pick(...)
複製代碼

但這種方案有必定風險,能夠參考這篇文章:WebView中接口隱患與手機掛馬利用 在 Android4.2 以後提供了 @JavascriptInterface 註解,暴露給 JS 的方法必需要帶上這個。 因此前面的 _pick 方法須要帶上這個註解。

private class PickerJSBridge {
    @JavascriptInterface
    public void _pick(...) {
    }
}
複製代碼

Native 調用 JS

Native 調用 JS 通常就是直接 JS 代碼字符串,有些相似咱們調用 JS 中的 eval 去執行一串代碼。通常有 loadUrlevaluateJavascript 等幾種方法,這裏逐一介紹。 可是無論哪一種方式,客戶端都只能拿到掛載到 window 對象上面的屬性和方法。

Android

在 Android 裏面須要區分版本,在安卓4.4以前的版本支持 loadUrl,使用方式相似咱們在 a 標籤的 href 裏面寫 JS 腳本同樣,都是javascript:xxx 的形式。這種方式沒法直接獲取返回值。

webView.loadUrl("javascript:foo()")
複製代碼

在安卓4.4以上的版本通常使用 evaluateJavascript 這個 API 來調用。這裏須要判斷一下版本。

if (Build.VERSION.SDK_INT > 19) //see what wrapper we have
{
    webView.evaluateJavascript("javascript:foo()", null);
} else {
    webView.loadUrl("javascript:foo()");
}
複製代碼

UIWebView

在 iOS 的 UIWebView 裏面使用 stringByEvaluatingJavaScriptFromString 來調用 JS 代碼。這種方式是同步的,會阻塞線程。

results = [self.webView stringByEvaluatingJavaScriptFromString:"foo()"];
複製代碼

WKWebView

WKWebView 可使用 evaluateJavaScript 方法來調用 JS 代碼。

[self.webView evaluateJavaScript:@"document.body.offsetHeight;" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
    // 獲取返回值 response
    }];
複製代碼

JS Bridge 設計

前面講完了 JS 和 Native 互調的全部方法,這裏來介紹一下咱們這邊 JS Bridge 的設計吧。 咱們這邊的 JS Bridge 通訊是基於 WebViewJavascriptBridge 這個庫來實現的。主要是結合 Scheme 協議+上下文注入來作。考慮到 Android 和 iOS 不同的通訊方式,這裏進行了封裝,保證提供給外部的 API 一致。 具體功能的調用咱們封裝成了 npm 包,下面的是幾個基礎 API:

  1. callHandler(name, params, callback):這個是調用 Native 功能的方法,傳模塊名、參數、回調函數給 Native。
  2. hasHandler(name):這個是檢查客戶端是否支持某個功能的調用。
  3. registerHandler(name):這個是提早註冊一個函數,等待 Native 回調,好比 pageDidBack 這種場景。

那麼這幾個 API 又是如何實現的呢?這裏 Android 和 iOS 封裝不一致,應當分開來講。

Android Bridge

前面咱們有說過安卓能夠經過 @JavascriptInterface 註解來將對象和方法暴露給 JS。因此這裏的幾個方法都是經過註解暴露給 JS 來調用的,在 JS 層面作了一些兼容處理。

hasHandler

首先最簡單的是這個 hasHandler,就是在客戶端裏面維護一張表(其實咱們是寫死的),裏面有支持的 Bridge 模塊信息,只須要用 switch...case 判斷一下就好了。

@JavascriptInterface
public boolean hasHandler(String cmd) {
        switch (cmd) {
            case xxx:
            case yyy:
            case zzz:
                return true;
        }
        return false;
    }
複製代碼

callHandler

而後咱們來看 callHandler 這個方法,它是提供 JS 調用 Native 功能的方法。在調用這個方法以前,咱們通常須要先判斷一下 Native 是否支持這個功能。

function callHandler(name, params, callback) {
    if (!window.WebViewJavascriptBridge.hasHandler(name)) {
    }
}
複製代碼

若是 Native 沒有支持這個 Bridge,咱們就須要對回調進行兼容性處理。這個兼容性處理包括兩個方面,一個是功能方面,一個是 callback 的默認回參。

好比咱們調用 Native 的彈窗功能,若是客戶端沒支持這個 Bridge,或者咱們是在瀏覽器裏面打開的這個頁面,此時應該退出到使用 Web 的 alert 彈窗。對於 callback,咱們能夠默認給傳個 0,表示當前不支持這個功能。

假設這個 alert 的 bridge 接收兩個參數,分別是 titlecontent,那麼此時就應該使用瀏覽器自帶的 alert 展現出來。

function fallback(params, callback) {
    let content = `${params.title}\n{params.content}`
    window.alert(content);
    callback && callback(0)
}
複製代碼

這個 fallback 函數咱們但願可以更加通用,每一個調用方法都應該有本身的 fallback 函數,因此前面的 callHandler 應該設計成這樣:

function callHandler(name, params, fallback) {
    return function(...rest, callback) {
        const paramsList = {};
        for (let i = 0; i < params.length; i++) {
            paramsList[params] = rest[i];
        }
        if (!callback) {
            callback = function(result) {};
        }
        if (fallback && !window.WebViewJavascriptBridge.hasHandler(name))) {
            fallback(paramsList, callback);
        } else {
            window.WebViewJavascriptBridge.callHandler(name, params, callback);
        }
    }
}
複製代碼

咱們能夠基於這個函數封裝一些功能方法,好比前面的 alert:

function fallback(params, callback) {
    let content = `${params.title}\n{params.content}`
    window.alert(content);
    callback && callback(0)
}

function alert( title, content, cb: any ) {
  return callHandler(
    'alert',
    ['title', 'content'],
    fallback
  )(title, content, cb);
}
alert(`this is title`, `hahaha`, function() {
    console.log('success')
})
複製代碼

具體效果相似下面這種,這是從 Google 上隨便找的一張圖(侵刪):

image.png-8.3kB

那麼客戶端又如何實現回調 callback 函數的呢?前面說過,客戶端想調用 JS 方法,只能調用掛載到 window 對象上面的。

所以,這裏使用了一種很巧妙的方法,實際上 callback 函數依然是 JS 執行的。在調用 Native 以前,咱們能夠先將 callback 函數和一個 uniqueId 映射起來,而後存在 JS 本地。咱們只須要將 callbackId 傳給 Native 就好了。

function callHandler(name, data, callback) {
    const id = `cb_${uniqueId++}_${new Date().getTime()}`;
    callbacks[id] = callback;
    window.bridge.send(name, JSON.stringify(data), id)
}
複製代碼

在客戶端這裏,當 send 方法接收到參數以後,會執行相應功能,而後使用 webView.loadUrl 主動調用前端的一個接收函數。

@JavascriptInterface
public void send(final String cmd, String data, final String callbackId) {
    // 獲取數據,根據 cmd 來調用對應功能
    // 調用結束後,回調前端 callback
    String js = String.format("javascript: window.bridge.onReceive(\'%1$s\', \'%2$s\');", callbackId, result.toDataString());
    webView.loadUrl(js);
}
複製代碼

因此 JS 須要事前定義好這個 onReceive 方法,它接收一個 callbackId 和一個 result。

window.bridge.onReceive = function(callbackId, result) {
    let params = {};
    try {
        params = JSON.parse(result)
    } catch (err) {
        //
    }
    if (callbackId) {
        const callback = callbacks[callbackId];
        callback(params)
        delete callbacks[callbackId];
    }
}
複製代碼

大體流程以下:

image.png-146.5kB

registerHandler

註冊的流程比較簡單,也是咱們把 callback 函數事先存到一個 messageHandler 對象裏面,不過此次的 key 再也不是一個隨機的 id,而是 name

function registerHandler(handlerName, callback) {
    if (!messageHandlers[handlerName]) {
      messageHandlers[handlerName] = [handler];
    } else {
      // 支持註冊多個 handler
      messageHandlers[handlerName].push(handler);
    }
}
// 檢查是否有這個註冊能夠直接檢查 messageHandlers 裏面是否有
function hasRegisteredHandler(handlerName) {
    let has = false;
    try {
      has = !!messageHandlers[handlerName];
    } catch (exception) {}
      return has;
    }
複製代碼

這裏不像 callHandler 須要主動調用 window.bridge.send 去通知客戶端,只須要等客戶端到了相應的時機來調用 window.bridge.onReceive 就好了。 因此這裏還須要改造一下 onReceive 方法。因爲再也不會有 callbackId 了,因此客戶端能夠傳個空值,而後將 handlerName 放到 result 裏面。

window.bridge.onReceive = function(callbackId, result) {
    let params = {};
    try {
        params = JSON.parse(result)
    } catch (err) {
        //
    }
    if (callbackId) {
        const callback = callbacks[callbackId];
        callback(params)
        delete callbacks[callbackId];
    } else if (params.handlerName)(
        // 可能註冊了多個
        const handlers =  messageHandlers[params.handlerName];
        for (let i = 0; i < handlers.length; i++) {
            try {
                delete params.handlerName;
                handlers[i](params);
            } catch (exception) {
            }
        }
    )
}

複製代碼

這種狀況下的流程以下,能夠發現徹底不須要 JS 調用 Native:

image.png-131.2kB

iOS Bridge

講完了 Android,咱們再來說講 iOS,本來 iOS 能夠和 Android 設計一致,但是因爲種種緣由致使有很多差別。

iOS 和 Android 中最顯著的差別就在於這個 window.bridge.send 方法的實現,Android 裏面是直接調用 Native 的方法,iOS 中是經過 URL Scheme 的形式調用。

協議依然是 WebViewJavaScriptBridge 裏面的協議,URL Scheme 自己不會傳遞數據,只是告訴 Native 有新的調用。

而後 Native 會去調用 JS 的方法,獲取隊列裏面全部須要執行的方法。

因此咱們須要事先建立好一個 iframe,插入到 DOM 裏面,方便後續使用。

const CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme';
const QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__';
function _createQueueReadyIframe(doc) {
    messagingIframe = doc.createElement('iframe');
    messagingIframe.style.display = 'none';
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    doc.documentElement.appendChild(messagingIframe);
  }
複製代碼

callHandler

每次調用的時候只須要複用這個 iframe 就好了。這裏是處理 callback 並通知 Native 的代碼:

function callHandler(handlerName, data, responseCallback) {
    _doSend({ handlerName: handlerName, data: data }, responseCallback);
  }
function _doSend( message, callback ) {
    if (responseCallback) {
      const callbackId = `cb_${uniqueId++}_${new Date().getTime()}`;
      callbacks[callbackId] = callback;
      message['callbackId'] = callbackId;
    }
    sendMessageQueue.push(message);
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
  }
複製代碼

通知 Native 以後,它怎麼拿到咱們的 handlerNamedata 呢?咱們能夠實現一個 fetchQueue 的方法。

function _fetchQueue() {
    const messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;
  }
複製代碼

而後將其掛載到 window.WebViewJavascriptBridge 對象上面。

window.WebViewJavascriptBridge = {
    _fetchQueue: _fetchQueue
  };
複製代碼

這樣 iOS 就可使用 evaluateJavaScript 輕鬆拿到這個 messageQueue

- (void)flushMessageQueue
{
    [_webView evaluateJavaScript:@"WebViewJavascriptBridge._fetchQueue();" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        [self _flushMessageQueue:result];
    }];
}
- (void)_flushMessageQueue:(id)messageQueueObj
{
    // 解析 messageQueueString
    // 根據傳入的 handlerName 執行對應操做
}
複製代碼

那麼 iOS 又是如何回調 JS 的 callback 函數呢?這個其實和 Android 的 onReceive 是一樣的原理。這裏能夠實現一個 _handleMessageFromObjC 方法,一樣掛載到 window.WebViewJavascriptBridge 對象上面,等待 iOS 回調。

function _dispatchMessageFromObjC(messageJSON) {
    const message = JSON.parse(messageJSON);
    if (message.responseId) {
        var responseCallback = callbacks[message.responseId];
        if (!responseCallback) {
          return;
        }
        responseCallback(message.responseData);
        delete callbacks[message.responseId];
     }
}
複製代碼

流程以下:

image.png-149.2kB

registerHandler

registerHandler 和 Android 原理是如出一轍的,都是提早註冊一個事件,等待 iOS 調用,具體就很少講了,這裏直接放代碼:

// 註冊
function registerHandler(handlerName, handler) {
    if (typeof messageHandlers[handlerName] === 'undefined') {
      messageHandlers[handlerName] = [handler];
    } else {
      messageHandlers[handlerName].push(handler);
    }
  }
// 回調
function _dispatchMessageFromObjC(messageJSON) {
    const message = JSON.parse(messageJSON);
    if (message.responseId) {
        var responseCallback = callbacks[message.responseId];
        if (!responseCallback) {
          return;
        }
        responseCallback(message.responseData);
        delete callbacks[message.responseId];
     } else if (message.handlerName){
        handlers = messageHandlers[message.handlerName];
        for (let i = 0; i < handlers.length; i++) {
          try {
            handlers[i](message.data, responseCallback);
          } catch (exception) {
          }
        }
     }
}
複製代碼

總結

這些就是 Hybrid 裏面 JS 和 Native 交互的大體原理,忽略了很多細節,好比初始化 WebViewJavascriptBridge 對象等等,感興趣的也能夠參考一下這個庫:JsBridge

相關文章
相關標籤/搜索