Принцип и практика коммуникации JS Bridge

JavaScript

предисловие

В предыдущей статье были представлены соответствующие технологии разработки мобильных терминалов.Эта статья в основном начинается с связи JS Bridge, разработанной Hybrid.

Как следует из названия, JS Bridge означает мост, который является мостом, соединяющим JS и Native, а также ядром гибридного приложения. Обычно он делится на две формы: JS, вызывающий Native, и Native, активно вызывающий JS.

URL Scheme

Схема URL – это специальный URL-адрес, который обычно используется для пробуждения приложения в веб-интерфейсе или даже для перехода на определенную страницу приложения. Например, при оплате на мобильном веб-сайте вы можете напрямую вызвать платеж Alipay. страница.

Вот небольшой пример, вы можете ввести прямо в браузереweixin://, система предложит вам открыть WeChat. войтиmqq://Это поможет вам разбудить мобильный QQ.

image.png-153.6kB

Вот краткий обзор часто используемых схем URL-адресов приложений:Коллекция URL-схем

Открыв эту страницу на своем мобильном телефоне, нажмите здесь, и вам будет предложено открыть WeChat.

Мы часто говорим, что Deeplink обычно реализуется на основе схемы URL. Структура URI выглядит следующим образом:

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, вы также можете настроить протокол. Чтобы позаимствовать картинку из Википедии:

image.png-62.8kB

Обычно после установки приложения в системе мобильного телефона регистрируется схема, напримерweixin://Таким образом, когда мы получаем доступ к этому адресу схемы в мобильном браузере, система вызывает наше приложение.

Как правило, в Android вам необходимо зарегистрировать Scheme в файле AndroidManifest.xml:

<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.Некоторые уже используются системой и не должны использоваться, например Карты, YouTube, Музыка. Для получения дополнительной информации см. документацию на официальном веб-сайте Apple:Defining a Custom URL Scheme for Your App

image.png-74.6kB

JS вызывает натив

В iOS необходимо различать UIWebView и WKWebView два WebView:

image.png-47.2kB

WKWebView появился после iOS8, цель — заменить громоздкий UIWebView, он занимает меньше памяти, около 1/3 от UIWebView, поддерживает лучшие возможности HTML5 и имеет более мощную производительность. Но есть и некоторые минусы, такие как отсутствие поддержки кеширования, нужно вставлять куки самому, нельзя отправлять POST-запросы с параметрами, нельзя парсить параметры при перехвате POST-запросов и так далее.

Есть примерно три способа, которыми JS может вызвать нативную коммуникацию:

  1. Схема перехвата
  2. Блокировка всплывающих окон
  3. Внедрить контекст JS

Эти три метода, как правило, имеют свои преимущества и недостатки, которые будут представлены один за другим ниже.

Схема перехвата

Подумайте хорошенько, если мы передаем данные между JS и Java, что нам делать? Для фронтенд-разработки наиболее частым требованием является настройка интерфейса запросов Ajax. Независимо от того, является ли другая сторона Java или Python, мы можем получать данные через интерфейс http/https. На самом деле этот процесс больше похож на JSONP.

Известно, что клиент может перехватывать запросы, так можно ли поднимать шумиху по этому поводу?

Что, если мы запросим несуществующий адрес с некоторыми параметрами на нем и укажем клиенту функцию, которую нам нужно вызвать через параметры?

Например, я хочу вызвать функцию сканирования кода:

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

Клиент может перехватить этот запрос и проанализировать указанные выше параметры.funcчтобы определить, какая функция должна быть вызвана в данный момент. После того, как клиент активирует функцию сканирования кода, он получит объект обратных вызовов в WebView и вызовет его обратно в соответствии с 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

Доступно на стороне AndroidshouldOverrideUrlLoadingдля перехвата 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внутриonJsAlert,onJsConfirm,onJsPromptМетоды перехватывают и анализируют поступающие сообщения.

// 拦截 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);
    }
 }

Недостатком этого метода является то, что UIWebView не поддерживается на iOS, а WKWebView лучшеscriptMessageHandler, скорее обидно.

внедрить контекст

Ранее мы говорили о встроенном в iOS фреймворке JavaScriptCore, который может реализовывать такие функции, как выполнение JS и внедрение Native-объектов.

Этот метод не полагается на перехват, он в основном внедряет объекты и методы в контекст JS через WebView, позволяя JS напрямую вызывать натив.

PS: Block в iOS — это реализация 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, который можно вызывать при уничтожении WebViewremoveScriptMessageHandlerуничтожить этот объект. После того, как внешний интерфейс вызовет введенный собственный метод, вы можете передать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 с прямой передачей, без строки.

Android addJavascriptInterface

Внедрение JS до Android 4.2 обычно выполняется с использованиемaddJavascriptInterface, и предыдущийaddScriptMessageHandlerЕсть несколько похожих, но без своих ограничений.

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

Вызовите это в JS:

window.DatePickerBridge._pick(...)

Но эта схема имеет определенные риски, вы можете обратиться к этой статье:Скрытые опасности интерфейса в WebView и использование висячих лошадей мобильного телефонаПредоставляется после Android 4.2@JavascriptInterfaceАннотация, методы, открытые для JS, должны нести это. Итак, предыдущий_pickМетод должен содержать эту аннотацию.

private class PickerJSBridge {
    @JavascriptInterface
    public void _pick(...) {
    }
}

Нативные вызовы JS

Собственные вызовы JS обычно являются прямыми строками кода JS, которые чем-то похожи на вызовы JS.evalдля выполнения строки кода. обычно имеютloadUrl,evaluateJavascriptНесколько других методов представлены здесь один за другим. Но в любом случае, клиент может получить монтирование только дляwindowСвойства и методы объектов.

Android

В Android необходимо различать версию, версия до Android 4.4 поддерживает loadUrl, и метод использования аналогичен тому, что в теге a.hrefЭто то же самое, что писать в нем JS-скрипты.javascript:xxxформа. Таким образом, возвращаемое значение не может быть получено напрямую.

webView.loadUrl("javascript:foo()")

Обычно используется в версиях выше Android 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

Используется в UIWebView на iOSstringByEvaluatingJavaScriptFromStringдля вызова 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-дизайн моста

Я закончил все методы интермодуляции между 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

Ранее мы говорили, что Android может пройти@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 эту функцию.

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

Если Native не поддерживает этот мост, нам нужно выполнить обработку совместимости при обратном вызове. Эта обработка совместимости включает два аспекта: один аспект функции, а другой — возвращаемый параметр обратного вызова по умолчанию.

Например, если мы вызываем всплывающую функцию Native, если клиент не поддерживает этот мост или если мы открываем страницу в браузере, мы должны в это время выйти в использование Интернета.alertВсплывающие окна. Для обратного вызова мы можем передать 0 по умолчанию, указывая, что эта функция в настоящее время не поддерживается.

Предположим, этоalertМост получает два параметра, которыеtitleа такжеcontent, то вы должны использовать собственный браузер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);
        }
    }
}

Мы можем инкапсулировать некоторые функциональные методы на основе этой функции, такие как предыдущее предупреждение:

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

Так как же клиент реализует функцию обратного вызова? Как упоминалось ранее, если клиент хочет вызвать метод JS, он может вызвать и подключиться только кwindowнад объектом.

Следовательно, вот очень умный метод, на самом деле, функция обратного вызова все еще JS выполнение. Перед вызовом уроженца мы можем сопоставить функцию обратного вызова и UniqueId, а затем есть локальные JS. Нам нужно только передать Callbackid для родных.

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

На стороне клиента, когда метод отправки получит параметры, он выполнит соответствующую функцию, а затем использует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 и результат.

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

Процесс регистрации относительно прост, и мы также сохраняем функцию обратного вызова вmessageHandlerобъект, но на этот раз ключ уже не случайный 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Поместите это в результат.

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.

Протокол по-прежнему остается протоколом в WebViewJavaScriptBridge.Сама схема URL-адресов не будет передавать данные, а просто сообщит 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 каждый раз, когда вы его вызываете. Вот код, который обрабатывает обратный вызов и уведомляет 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, как он получает нашиhandlerNameа такжеdataШерстяная ткань? мы можем достичь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? Это на самом деле то же самое, что и AndroidonReceiveтот же принцип. Здесь можно добиться_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) {
          }
        }
     }
}

Суммировать

Это общие принципы взаимодействия JS и Native в Hybrid, игнорируя многие детали, такие как инициализацияWebViewJavascriptBridgeОбъекты и т. д. Если вам интересно, вы также можете обратиться к этой библиотеке:JsBridge