предисловие
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];
/*
webView协议中的方法
shouldStartLoadWithRequest //准备加载内容时调用的方法,通过返回值来进行是否加载的设置
webViewDidStartLoad //开始加载时调用的方法
webViewDidFinishLoad //结束加载时调用的方法
didFailLoadWithError //加载失败时调用的方法
*/
- (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