JSBridge в действии

внешний интерфейс JavaScript Android iOS

предисловие

H5 VS Native всегда был спорной темой в технологическом сообществе. Технологические стеки, такие как react и vue, ведут разработку чисто H5, в то время как rn и week пропагандируют нативный опыт. Но в реальном проектном бою часто выбирается нейтральное решение: гибридная разработка. Народное название: Гибрид.

В настоящее время я занимаюсь исследованием и разработкой новостных продуктов, для всех я знаком с Toutiao, Baidu News, NetEase News и т.д. На ранней стадии разработки продукта, учитывая сложность реализации (например, страницы с подробными сведениями о новостях, смешанные изображения и текст, NA реализовать не так просто, как H5), некоторые части выбрали гибридный метод для разработки, в этой статье будут обсуждаться некоторые процесса разработки. Поделитесь своими идеями для ознакомления.

Проблемы, решаемые JSBridge

Для гибридной разработки наиболее важными вопросами являются:Двусторонняя связь между H5 и Native.Но на самом деле способ взаимодействия между JS и NA очень ограничен, о чем будет подробно рассказано ниже. В процессе разработки, если это просто простой вызов метода, он не может ни гарантировать успешность вызова, ни гарантировать, что код будет достаточно кратким. Итак, есть JSBridge. JSBridge — это своего рода мост, реализованный JS, это своего рода идея, которая может иметь разное понимание и разную реализацию кода. Основная идея состоит в том, чтобы построить мост между H5 и NA, оставив более дружественный и разумный интерфейс для обеих сторон.

Общий метод двунаправленной связи между H5 и NA

Способы связи и совместимость H5 показаны в таблице ниже. Это относится к загрузке страниц H5 с помощью собственного веб-просмотра и реализации обмена сообщениями между H5 и NA через API, перехват URL-адресов, глобальные вызовы и т. д. С точки зрения крупного производителя, в реальном бою будет выбран более совместимый метод.

H5 вызывает метод NA для гребенки

Платформа метод Примечание
Android shouldOverrideUrlLoading метод перехвата схемы
Android addJavascriptInterface API
Android onJsAlert(), onJsConfirm(), onJsPrompt()
IOS Блокировать URL-адреса
IOS(UIwebview) JavaScriptCore Метод API, поддержка iOS7 +
IOS(WKwebview) window.webkit.messageHandlers Метод API, поддержка IOS8+

NA вызывает метод H5 для расчесывания

Платформа метод Примечание
Android loadurl()
Android evaluateJavascript() Android 4.4 +
IOS(UIwebview) stringByEvaluatingJavaScriptFromString
IOS(UIwebview) JavaScriptCore IOS7.0+
IOS(Wkwebview) evaluateJavaScript:javaScriptString iOS8.0+

Изучив приведенную выше таблицу методов вызова с обоих концов, нетрудно проанализировать, что перехват URL-адресов и выполнение JS является более распространенным и совместимым решением для Android и IOS. На этом подходе основана наша гибридная разработка.

Традиционные идеи гибридной разработки

С точки зрения связи между H5 и NA, самая простая и прямая идея такова: NA перехватывает сообщение получения URL-адреса H5 (обычно путем изменения src iframe ①), и после бизнес-обработки NA выполняет JS (глобальный метод, зарегистрированный в продвижение на стороне H5) ③) Уведомление об обратном вызове H5 (как показано ниже).

Код H5 реализован следующим образом:

<html>
...
<body>
    <div class="content">XXXXX</div>
</body>
  
<script>
    // ① 注册全局函数,以便端调用
    window.setAllContent = function(){
         
    }
 
    // ② 通用方法函数
    var sendschema = function(action,param){
        let tempnode = document.createElement('iframe');
        tempnode.src = "bdnews://"+action+param;
    }
 
    // ③ H5逻辑开始 运行函数
    document.addEventListener("DOMContentLoaded",function(){
        sendschema('load_finish');
    },false);
</script>
  
...
</html>

Принцип Android примерно такой:

webView.setWebViewClient(new WebViewClient() {
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
         
        // 场景一: 拦截请求、接收schema
        if (url.equals("load_url")) {
           
            // 处理逻辑
            dosomething
  
            // 回掉
            view.loadUrl("javascript:setAllContent(" + json + ");")
        }
  
  
        // 场景二:端自己调用H5,没有请求发起
        clickbutton(){
            view.loadUrl("javascript:setAllContent(" + json + ");")
        }
    }
});

Примерная логика IOS такова:

// 初始化webview
UIWebView * view = [[UIWebView alloc]initWithFrame:self.view.frame];
[view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.xx.com"]]];
[self.view addSubview:view];&nbsp;
&nbsp;
/*
webView协议中的方法
shouldStartLoadWithRequest //准备加载内容时调用的方法,通过返回值来进行是否加载的设置
webViewDidStartLoad //开始加载时调用的方法
webViewDidFinishLoad //结束加载时调用的方法
didFailLoadWithError //加载失败时调用的方法
*/
&nbsp;
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    if ([urlString hasPrefix:@"scheme://hybrid?info="]) {
        if([name isEqualToString:@"load_finish"]){
            // [self.webView setContent];
            [self.webView stringByEvaluatingJavaScriptFromString:strFormat];
        }
    }
}
 
- clickbutton(){
    [self.webView setContent];
}

Но есть и болевые точки в этом развитии:

1)Функция обратного вызова неоднозначна. Можно сказать, что в настоящее время не существует механизма для функций обратного вызова, что приводит к некоторым случаям, которые полагаются на функции обратного вызова для анализа и оценки, например: вызывающая функция, успешность вызова, обработка исключений при сбое вызова и т. д. ;

2)Корреспонденция не ясна. Есть некоторые вызовы, которые выглядят как обратные вызовы, но не объединяют их вместе, что приводит к беспорядочному коду, который трудно поддерживать. Как показано в приведенной выше демонстрации: sendschema('load_finish') и setAllContent изначально предназначались для того, чтобы сообщить NA, что страница готова, и после того, как NA ее получит, вставить данные на страницу. Пара функций, которые изначально были тесно связаны, не может быть видна, если они разделены;

3)Многословность глобальной функции.理想中如果调用和回调成对出现,DEMO中注册及维护全局函数的工作就会减少很多。提升页面可读性和维护成本。如 load_finish 和 setAllContent,只保留 load_finish 即可;

4)Сквозной код. Метод вызова, согласованный с H5, регистрируется в терминале, и, очевидно, также необходимо поддерживать набор кодов, чтобы определить, когда звонить.

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

Разработка схемы JSB

Между H5 и NA добавляется промежуточный слой, который инкапсулирует взаимодействие между связью H5 и NA. H5 и NA не заботятся о внешнем виде друг друга и могут вызывать функцию через метод, предоставляемый средним уровнем.

Модель взаимодействия JSB

Взаимодействие между H5 и NA можно условно разделить на две категории с точки зрения H5: возврата нет и возврата нет, и возврата нет.

Первый тип модели взаимодействия

Логика запроса: возврата нет, а есть возврат. Есть две схемы реализации (первоначальный вариант такой):

① Ассоциация имени функции

let BDAPPnode = {
   callbacks: {},
   // 调用函数注册
   invoke(action, params, successfnname, successfn) {
       this.callbacks[successfnname] = {
           success: successfn
       };
       sendschema(action, params);
   },
 
   // NA调用
   callbackSuccess(callbackname, params) {
       try {
           BDAPPnode.callbackFromNative(callbackname, params, true);
       } catch (e) {
           console.log('Error in error callback: ' + callbackname + ' = ' + e);
       }
   },
   callbackFromNative(callbackname, params, isSuccess) {
       let callback = this.callbacks[callbackname];
       if (callback) {
           if (isSuccess) {
               callback.success && callback.success(params);
           }
       };
   }
};

② Идентификационная ассоциация

let BDAPPnode = {
   callbackId: Math.floor(Math.random() * 2000000000),
   callbacks: {},
   invoke(action, params, onSuccess, onFail) {
       this.callbackId++;
       this.callbacks[self.callbackId] = {
           success: onSuccess,
           fail: onFail
       };
       sendschema(action, params, this.callbackId);
   },
   callbackSuccess(callbackId, params) {
       try {
           BDAPPnode.callbackFromNative(callbackId, params, true);
       } catch (e) {
           console.log('Error in error callback: ' + callbackId + ' = ' + e);
       }
   },
   callbackError(callbackId, params) {
       try {
           BDAPPnode.callbackFromNative(callbackId, params, false);
       } catch (e) {
           console.log('Error in error callback: ' + callbackId + ' = ' + e);
       }
   },
   callbackFromNative(callbackId, params, isSuccess) {
       let callback = this.callbacks[callbackId];
       if (callback) {
           if (isSuccess) {
               callback.success && callback.success(callbackId, params);
           } else {
               callback.fail && callback.fail(callbackId, params);
           }
           delete BDAPPnode.callbacks[callbackId];
       };
   }
};

При выполнении запроса зарегистрируйте метод обратного вызова. Это делается с двумя целями:

  • Нет необходимости заранее регистрировать все глобальные функции обратного вызова, что сокращает ненужную инициализацию и тем самым сокращает время белого экрана;

  • Имя callback-функции добавлять не нужно, при инициировании запроса передавать случайный ID и одновременно регистрировать callback-функцию этого ID. NA выполняет логику обратного вызова с помощью унифицированного инкапсулированного вызова функции обратного вызова, идентификатора обратного вызова и параметров.

Какой из них выбрать, зависит от конкретной ситуации.

Второй тип модели взаимодействия рассматривает

Логика запроса: возврата нет, запрос не выдается, NA активно звонит. Этот класс также должен зарегистрировать глобальные переменные и дождаться вызова NA. Это то же самое, что и реализация не-JSBridge

window.fn1 = () =>{
   // do fn1
}
  
window.fn2 = () =>{
   // do fn2
}

выбор плана

Фактический процесс глубокого опыта, развитие смешанного использования можно разделить на две категории:NA обслуживает H5, H5 обслуживает NA.

Первый — это в основном H5, и большинство взаимодействий заключается в том, что H5 инициирует запросы NA и ожидает обратных вызовов NA, которые можно назвать: «запрос один к одному», например: запросы H5 для получения географического местоположения и возвращает N\S координаты после завершения NA;

Последнее в основном предназначено для решения проблемы высокой стоимости реализации NA, и в основном NA заранее активно вызывает зарегистрированный метод H5, что можно назвать «индивидуальным запросом», чтобы обеспечить плавную реализацию функции.

В реальном боевом процессе проекта часто возникает такая ситуация: функция обратного вызова представляет собой не только запрос один к одному, но и отдельный вызов, например функцию комментария, который можно отправить, нажав на вход NA на странице или нажав кнопку, реализованную NA в нижней панели BAR., чтобы отправить. Все страницы должны быть обновлены. Я надеюсь, что с точки зрения H5 можно отличить NA, а комментарии вызовов страницы H5 успешно отличить от комментариев вызовов NA, так что модель 1 и модель 2 можно будет разделить и реализовать независимо (и источник обновления страницы тоже можно выделить). Но с точки зрения NA, неважно, кто его повесил, пока комментарий проходит успешно, должен вызываться метод H5 страницы обновления. В противном случае NA необходимо передавать параметры с начала вызова до конца. После общения с концом обе стороны скомпрометировали один шаг, реализовав модель дифференциации источника 1 для простых функций и реализовав более сложную модель 2.

упаковка API

Уровень API является нижним уровнем и бизнесом JSBridge. Некоторые люди также считают его частью JSBridge. Для лучшего понимания я извлеку его отдельно. Здесь в основном инкапсулируются вызовы бизнес-уровня, как показано в следующем коде.

Еще одно слово здесь:В будние дни идея инкапсуляции и извлечения должна быть принята.С одной стороны, повторяющийся код уменьшается, а с другой стороны, код расслаивается за счет непрерывного извлечения.Ни один слой не может выполнять некоторую инкапсуляцию и расширение, что может улучшить повторное использование кода.

Момент впрыска JSB

инъекция АН

Мы определенно ожидаем, что JSB будет внедрен как можно раньше, чтобы его можно было вызвать в любое время в любом месте на странице внешнего интерфейса, а методы и время внедрения JS в NA относительно ограничены. Следующая таблица:

Платформа метод возможность
IOS[UI] [self.webView stringByEvaluatingJavaScriptFromString:injectjs] webViewDidFinishLoad (будут проблемы со временем)
IOS[wk] evaluateJavaScript:xxxx didCreateJavaScriptContext
Android webView.loadUrl("javascript:" + injectjs);) OnPageFinished

Существуют следующие методы для описания значения состояния страницы.В соответствии с совместимостью и целостностью реализации обычно используется DOMContentLoaded, а readystatechange используется для определения того, успешно ли загружена страница в IE9.

имя родительский объект описывать совместимость
DOMContentLoaded doc Содержимое страницы в порядке IE9+
onload win Страница загружается сразу после завершения
readystatechange doc Статус загрузки страницы: неинициализированный: объект существует, но не был инициализирован. загрузка: объект загружает данные. загружен (loaded): Загрузка данных объекта завершена. интерактивный (интерактивный): объектом можно манипулировать, но он не загружается полностью. Complete (завершено): объект загружен IE9 и IE10 имеют ошибки реализации

uiwebview IOS предоставляет прокси WebViewDidFinishLoad, при вызове WebViewDidFinishLoad readyState может находиться в двух состояниях интерактивное и полное, поэтому будут проблемы с прямым вызовом страницы инициализации. Для этой проблемы, с точки зрения Северной Америки, вы можете реализовать расширение NSObject и реализовать webView:didCreateJavaScriptContext:forFrame. С точки зрения H5 можно определить статус страницы, и после завершения вызывается родной.

DidCreateJavaScriptContext в IOS и OnPageFinished в Android (загрузка страницы завершена) завершаются до загрузки веб-страницы, поэтому нет проблем с последовательностью вызовов этих двух моментов времени.

преимущество:

1) Регистрация ранняя, даже если конечная возможность вызывается при инициализации страницы, она может соответствовать требованиям

недостаток:

Поскольку мы выбрали uiwebview, то, следуя приведенным выше соображениям, у этого есть несколько недостатков. 1) Стоимость внедрения мониторинга высока 2) Требуется инъекция NA, NA не знаком с JS, а JS часто не знает логики NA, поэтому стоимость обслуживания неконтролируема.

Если не хватает времени, есть ли другой способ, кроме инъекции NA?

JS-инъекция

На самом деле, JS также можно внедрить в начале страницы. Например, извлеченный код Jsbridge применяется непосредственно в голове.На этот раз мы приняли эту схему даунгрейда в 8.0 и завершили построение архитектуры за короткое время.

преимущество:

Таким образом снижаются затраты на обслуживание, выполняются функции и повышается вероятность успешного вызова.

недостаток:

Добавлено время анализа загрузки страницы, чтобы повлиять на время белого экрана.

Суммировать

Гибрид — это способ соединения H5 и NA, который может быстро повторять функции H5 и иметь опыт NA.Это типичный способ разработки гибридной разработки. В практическом процессе реализация кода должна быть настроена в соответствии с моделью бизнес-формы, а время внедрения не статично и может быть выбрано в соответствии с бизнес-формой.

использованная литература

Jsbridge в развитии смешивания

Удаленный вызов процесса

Способ, которым вы хотите взаимодействовать с WebView и JS, здесь

UIWebView взаимодействует с WKWebView, JavaScript и OC

Подробное объяснение использования UIWebView в iOS

Время и положение внедрения кода UIWebView

Гибридная разработка

Яма, используемая JavaScriptCore в реальных проектах