При разработке мобильного приложения iOS или Android рано или поздно может встать вопрос: «Реализовать фичу на WebView или же нативно?». В некоторых случаях ответ лежит на поверхности, но, к сожалению, так бывает не всегда. А если очень велик соблазн предоставить пользователям новый функционал поскорее — это может склонить к неправильному решению, с которым впоследствии предстоит что-то сделать.
Сегодня мы хотим поделиться с вами тем, какую стратегию мы выбрали в Циан для себя и как к ней пришли. Посмотрим, где же мы поставили запятую 🙂
Что такое Android System WebView?
WebView раньше был основной частью операционной системы Android, которую можно было обновить только путем обновления до новой версии Android. Однако Google отделил WebView от базовой ОС с помощью Android 5.0 Lollipop, поэтому обновления для WebView приходилось загружать через магазин приложений Google Play. Затем он переместил компонент System WebView в Chrome с появлением Android 7.0 Nougat, поэтому вы можете обновить приложение только путем обновления Chrome. Затем он переместил его обратно в отдельное приложение Android System WebView с Android 10, где оно и осталось.
Основы WebView
В двух словах об основах WebView. Рассмотрим четыре строки кода:
Тут видно, что мы получаем WebView и загружаем в него сайт example.com, вызвав WebView.loadURL(). В Android есть два важных класса: WebViewClient и WebChromeClient, которые взаимодействуют с WebView. Зачем они нужны? WebViewClient требуется для того, чтобы контролировать процесс загрузки страницы, а WebChromeClient — чтобы взаимодействовать с этой страницей после того, как она успешно загружена. До завершения загрузки страницы работает WebViewClient, а после — WebChromeClient. Как видно в коде, для того чтобы взаимодействовать с WebView, нам нужно создать собственные инстансы этих классов и передать их в WebView. Далее WebView при определенных условиях вызывает разные методы, которые мы переопределили в наших инстансах, и так мы узнаем о событиях в системе.
Наиболее важные методы, которые WebView вызывает у созданных нами инстансов WebViewClient и WebChromeClient:
О назначении всех этих методов буду рассказывать немного позднее, хотя из самих названий уже многое понятно.
Что такое WebView?
WebView — это компонент платформы Android, который позволяет встраивать web-страницы в Android-приложения. По сути, это встраиваемый браузер. При помощи WebView примерно год назад мы решили создать ICQ Web API для интеграции web-приложений в наш мессенджер. Что представляет собой web-приложение? По сути, это HTML-страница, которая содержит JavaScript и работает внутри ICQ. Используя ICQ Web API, web-страницы через JavaScript могут отдавать ICQ разные команды, допустим, на отправку сообщений, открытие чата и т.п.
Вот как это выглядит в ICQ. Из пункта Applications можно перейти в список приложений. Это пока еще не WebView, чтобы попасть в него, нужно выбрать одно из приложений. Тогда мы переходим непосредственно в WebView, куда web-приложение загружается из сети.
Как это устроено технически? У WebView есть возможность определенным образом инжектировать Java код в JavaScript. JavaScript может вызывать код, который мы написали и предоставили ему. Это возможность, на которой и основан весь ICQ Web API.
Здесь показано, что внутри ICQ работает WebView, между ними есть инжектированный Java-класс, а в WebView загружаются приложения из сети.
Итак, JavaScript из WebView делает вызовы к Java-коду ICQ. Существует большое число различных вызовов, и в процессе разработки встретилось множество проблем, связанных с работой этого механизма, о которых я и расскажу далее.
То, что это годная вещь и оставалась в тени форума, теперь можно мало сомневаться, тем более что это и даже движком Хрома называют, обновил свой планшет до 5.1 Lollipop, Android реально движется в верном направлении, оптимизация, зарядка дольше держится, теперь и понятно, почему вроде таких новых плюшек, выпускают для Android, именно 5-го и только выше, изменения Android, происходят капитальные. Не тестировал, свой планшет на Android 4.4 в HTML 5 тесте, Flash ожидает, печальная участь, да сам Джобс заявлял что Флэшу, недолго суждено, кто в курсе событий. Лишь, после того, как накатил обновы отсюда, я догадался что предварительно следует прокатить Тест с версии Android System WebView при старой версии, вот результат:
Старая версия 43.0.2357.121:
Отмечу, забавный момент, поставив как-то Chrome Dev, где сейчас 48.х, тест не запускался, загрузка за загрузкой, Beta и Стабильная протестировать можно.
А вот что после обновы и из темы:
Что получается, на 20 баллов в тесте больше, неплохо, при общих та 518, обнова ASW – дело полезное, что касается обновы Android System WebView, странное ещё дело, обновить приложение из Маркета, нет в списках а при поиске обновить можно, а список того что у меня неподдерживается якобы приложений планшетом, немало, планшет топовый, а как то так, Твиттер тоже системный и неподдерживается, к этому можно отнести и WhatsApp даже (но это было на 4.4 прошивке). Если что, отписывайтесь, как у вас с HTML 5 и веб-контентом после обновы и до, также и в тесте чего как.
Сообщение отредактировал VLADFIONOV – 13.12.15, 06:32
Операционная система Android состоит из небольших бит и частей, и, как пользователь, вы можете наткнуться на такие приложения на вашем устройстве, поэтому вы должны знать, что это за приложение и является ли оно неотъемлемой частью необходимой для работы вашего устройства.
Если на вашем телефоне мало места, и вам необходимо удалить несколько приложений, вы можете решить, что нужно удалить эти приложения, но вы не знаете для чего они нужны и насколько они важны. Лучше не трогать эти приложения, так как их удаление может пагубно отразиться на работе вашего телефона.
Одним из таких системных приложений является Android System WebView, которое можно найти на устройстве с Android, работающее с Android 4.4, KitKat или новее.
Что такое Android System WebView? Что оно делает?
«Android System WebView — это компонент, поддерживающий Chrome, который позволяет приложениям показывать веб-компоненты в приложении, не открывая браузер и не закрывая приложения. Это приложение позволяет сторонним приложениям на вашем устройстве с Android открывать веб-страницы внутри самого приложения, а не открывать веб-страницу в браузере вашего мобильного устройства».
Интересно по теме: Как исправить ошибку: к сожалению, процесс com.android.phone остановлен инструкция здесь.
В Android 4.4 KitKat, WebView был изменен, чтобы сделать его более удобным для пользователей. В Android KitKat он был основан на Chromium и работал аналогично браузеру Chrome. После того, как KitKat преобразовали в Android 5.0 Lollipop, WebView превратился в отдельное приложение в Play Store, чтобы Google мог перенести обновления и исправления ошибок непосредственно в приложение, а не обновлять его из ОС, как раньше.
Определение состояния сети в JavaScript
В JavaScript есть полезный объект Navigator. У него есть поле onLine, показывающее статус сетевого подключения. Если у нас есть подключение к сети, в браузере это поле имеет значение true, в противном случае — false. Чтобы оно работало корректно внутри WebView, необходимо использовать метод WebView.setNetworkAvailable(). С его помощью мы передаем актуальное сетевое состояние, которое можно получить при помощи сетевого broadcast receiver или любым другим способом, которым вы трекаете сетевое состояние в Android. Делать это нужно постоянно. Если сетевое состояние изменилось, то нужно заново вызвать WebView.setNetworkAvailable() и передать актуальные данные. В JavaScript мы будем получать актуальное значение этого свойства через Navigator.onLine.
Контроль загрузки страницы в WebView
После того, как мы отдали WebView команду на загрузку страницы, следующим шагом нужно узнать результат выполнения: загрузилась ли страница. С точки зрения официальной Android-документации, все просто. У нас есть метод WebViewClient.onPageStarted(), который вызывается, когда страница начинает загружаться. В случае редиректа вызывается WebViewClient.shouldOverrideUrlLoading(), если страница загрузилась — WebViewClient.onPageFinished(), если не загрузилась — WebViewClient.onReceivedError(). Все кажется логичным. Как это происходит на самом деле?
На самом деле, все всегда по-разному и зависит от конкретного устройства: onPageStarted(), onPageFinished() и другие методы могут вызываться два раза, все методы могут вызываться в разном порядке, а некоторые могут не вызываться совсем. Особенно часто подобные проблемы возникают на Samsung и Google Nexus. Проблему эту приходится решать при помощи добавления дополнительных проверок в наш инстанс класса WebViewClient. Когда он начинает работать, мы сохраняем URL и затем проверяем, что загрузка происходит именно по этому URL. Если она завершилась, то проверяем на наличие ошибок. Так как код большой, приводить его не буду. Предлагаю посмотреть самостоятельно в примере, ссылка на который будет в конце.
JavaScript Alerts
По умолчанию диалог Alert в WebView не работает. Если загрузить туда страницу HTML с JavaScript и выполнить alert(‘Hello’), то ничего не произойдет. Чтобы заставить его работать, нужно определить свой инстанс WebChromeClient, переопределить метод WebChromeClient.onJSAlert() и в нем вызвать у него super.onJSAlert(). Этого достаточно, чтобы Alerts заработали.
Проблемы при работе с WebView
После старта загрузки обычно бывает нужно проконтролировать этот процесс: узнать, успешно ли прошла загрузка, были ли редиректы, отследить время загрузки и другие вещи. Также будет сказано о потоках, в которых работает JavaScript и вызовы в Java, о несоответствии типов Java и JavaScript, поведении Alerts в JavaScript и размерах передаваемых данных. Решение для этих проблем будет также описано дальше.
Инжектирование кода Java в JavaScript
Пример кода Java:
Пример кода JavaScript:
Если мы поставим breakpoint на строку return «Hello JavaStript!» и посмотрим имя потока, в котором получен вызов, какой это будет поток? Это не UI-поток, а специальный поток Java Bridge. Следовательно, если при вызове каких-то методов Java мы хотим манипулировать с UI, то нам нужно позаботиться о том, чтобы эти операции передавались в UI-поток — использовать хэндлеры или любой другой способ.
Второй момент: Java Bridge поток нельзя блокировать, иначе JavaScript в WebView просто перестанет работать, и никакие действия пользователя не будут иметь отклика. Поэтому если нужно делать много работы, задачи нужно также отправлять в другие потоки или сервисы.
Несоответствие типов Java в JavaScript
Когда мы вызываем некоторые методы, написанные на Java и инжектированные в JavaScript, как показано выше, возникает проблема несоответствия типов Java и JavaScript. В этой таблице приведены основные правила мапинга между системами типов:
Самое основное, что стоит здесь заметить, — то, что объектные обертки не передаются. А из всех Java-объектов в JavaScript мапится только String. Массивы и null в Java преобразуются в undefined в JavaScript.
С передачей в обратную сторону, из JavaScript в Java, тоже есть нюансы. Если вызывать какой-то метод, имеющий параметрами элементарные типы, то можно передать туда number. А если среди параметров метода есть не элементарные типы, а скажем, объектные обертки, такие как Integer, то такой метод не будет вызван. Поэтому нужно пользоваться только элементарными типами Java.
Обработка изменения ориентации устройства
Ещё одна серьезная проблема связана с портретной и альбомной ориентацией. Если поменять ориентацию устройства, то по умолчанию Activity будет пересоздана. При этом все View, которые к ней прикреплены, тоже будут пересозданы. Представьте ситуацию: есть WebView, в который загружена некая игра. Пользователь доходит до 99 уровня, поворачивает устройство, и инстанс WebView с игрой пересоздается, страница загружается заново, и он снова на первом уровне. Чтобы этого избежать, мы используем мануальную обработку смены конфигурации устройства. В принципе, это вещь известная и описана в официальной документации. Для этого достаточно прописать в AndroidManifest.xml в разделе активити параметр configChanges.
Это будет означать, что мы сами обрабатываем смену ориентации в activity. Если ориентация изменится, мы получаем вызов Activity.onConfigurationChange() и можем поменять какие-то ресурсы программно. Но обычно activity с WebView имеют только сам WebView, растянутый на весь экран, и там ничего делать не приходится. Он просто перерисовывается и все продолжает нормально работать. Таким образом, установка configChanges позволяет не пересоздавать Activity, и все View, которые в нем присутствуют, сохранят свое состояние.
Input type=”file”
Еще одна существенная проблема связана с объемом передаваемых данных между Java и JavaScript. Если передается достаточно большой объем данных (например, картинки) из JavaScript в Java, то при возникновении ошибки OutОfMemory, поймать ее не получится. Приложение просто падает. Вот пример того, что можно увидеть в logcat в этом случае:
Process com.estrongs.android.pop (pid 6941) has died
Process com.google.android.youtube (pid 24613) has died
Process kik.android (pid 3022) has died
Process com.doeasyapps.optimize (pid 30743) has died
Process com.sandisk.mz (pid 31340) has died
WIN DEATH: Window{3fc4769b u0 mc.test/mc.test. MainActivity}
WIN DEATH: Window{17a5c850 u0 mc.test/mc.test. DataSizeTestActivity}
Process mc.test (pid 16794) has died
Force removing ActivityRecord{215171e4 u0 mc.test/. DataSizeTestActivity t406}: app died, no saved state
Как видите, если в приложении происходит OutOfMemory, то начинают вылетать различные другие приложения, запущенные на устройстве. В итоге, закрыв все что можно, Android доходит до нашего приложения, и, так как оно находится в foreground, закрывает его последним. Еще раз хочу напомнить, что никакого исключения мы не получим, приложение просто упадет. Чтобы этого не происходило, необходимо ограничивать размер передаваемых данных. Многое зависит от устройства. На некоторых гаджетах получается передавать 6 мегабайт, на некоторых 2-3. Для себя мы выбрали ограничение в 1 мегабайт, и этого достаточно для большинства устройств. Если нужно передать больше, то данные придется резать на чанки и передавать частями.
Полноэкранный медиаплеер
Если в web-страницу встроен медиаплеер, то часто возникает потребность обеспечить возможность его работы в полноэкранном режиме. Например, медиаплеер youtube может работать внутри web-страницы в html-теге iframe, и у него есть кнопка переключения в полноэкранный режим. К сожалению, в WebView по умолчанию это не работает. Чтобы заставить это работать, нужно сделать несколько манипуляций. В xml layout, в котором расположен WebView, разместим дополнительно FrameLayout. Это контейнер, который растянут на весь экран и в котором будет находится View с плеером:
А затем в своем инстансе WebChromeClient переопределим несколько методов:
Система вызывает WebChromeClient.onShowCustomView(), когда юзер нажимает на кнопку перехода в полноэкранный режим в плеере. оnShowCustomView() принимает View, которое и репрезентует сам плеер. Этот View вставляется в FullScreenContainer и делается видимым, а WebView скрывается. Когда пользователь хочет вернуться из полноэкранного режима, вызывается метод WebChromeClient.onHideCustimView() и проделывается обратная операция: отображаем WebView и скрываем FullScreenContainer.
Пример
Итак, давайте рассмотрим, как работают наши рекомендации на популярном примере экрана «Лицензионные соглашения».
Почему его реализация на WebView оправдана:
Для данного экрана можно даже не пытаться загримировать WebView под натив. Мы так и поступили 🙂
Предыстория
У Циан есть мобильные приложения iOS, Android, ну и Web-сайт с адаптированной вёрсткой под разные типы устройств. У нас довольно большой департамент разработки, и он разделен на продуктовые направления, каждое из которых отвечает за свой функционал. Продуктовые направления максимально автономны и свободны в способах достижения своих бизнес-целей, в принятии решений, в выборе инструментов.
Но объединяет их одно — желание максимально быстро проверять свои гипотезы и максимальной скорости разработки фичей. В погоне за этим команды стали чаще прибегать к использованию WebView, особенно в рамках MVP-версий фичи. Порой возникали случаи, когда это промежуточное тестовое решение в нашем проекте несколько задерживалось 🙂
Стало ясно, что помимо общего желания скорости необходимо, чтобы у всех было общее понимание того, в каких случаях можно использовать WebView, а в каких лучше не стоит. И мы выработали для себя свод рекомендаций, единый для всех паттерн поведения, чтобы каждая автономная продуктовая вертикаль могла принять взвешенное решение по использованию WebView для своего кейса.
Наши рекомендации нисколько не претендуют на истину, мы их делали прежде всего для себя, исходя из наших условий: наша структура, требования бизнеса, наши проекты. Но вполне возможно, эти выработанные тезисы помогут вам выбрать технологии для фичи в вашем проекте. Если вы почерпнёте что-то новое для себя, что поможет добавить деталей вашей собственной стратегии использования WebView, мы будем особенно рады! )
На каждой из платформ у пользователя есть свои привычки и паттерны, поэтому, например, пользователю Android потребуется время, чтобы привыкнуть к iOS, и наоборот. Исходя из этого для нас важно во всех флоу максимально сохранять привычные пользователям каждой платформы основные UX-паттерны, такие как навигация, и поведения, поэтому в нашем случае WebView при использовании должен им соответствовать.
А теперь давайте разберёмся, к чему мы пришли. Поехали!
Вопросы и ответы
Вопрос: Есть проект CrossWalk — это сторонняя реализация WebView, позволяющая на старых устройствах использовать свежий Chrome. У вас есть какой-то опыт, вы пробовали его встраивать?
Ответ: Я не пробовал. На текущий момент мы поддерживаем Android начиная с 14-й версии и уже не ориентируемся на старые устройства.
Вопрос: Как вы боретесь с артефактами, которые остаются при прорисовке WebView?
Ответ: Мы с ними не боремся, пробовали — не получилось. Это происходит не на всех устройствах. Решили, что это не настолько вопиющая проблема, чтобы тратить на нее больше ресурсов.
Вопрос: Иногда требуется WebView вложить в ScrollView. Это некрасиво, но иногда требуется по заданию. Это не поощряется, даже где-то запрещается, и после этого возникают недостатки в работе. Но все равно иногда это приходится делать. Например, если вы сверху рисуете WebView, а под ним рисуете какой-то нативный компонент (который должен быть нативным согласно требованию), и все это должно быть выполнено в виде единого ScrollView. То есть сначала пользователь посмотрел бы всю страничку, а потом, если бы захотел, то долистал бы до этих нативных компонентов.
Ответ: К сожалению, не могу вам ответить, потому что я не сталкивался с такой ситуацией. Она довольно специфическая, и представить себе вариант, когда нужно WebView положить в ScrollView, мне сложно.
Вопрос: Есть почтовое приложение. Там сверху шапка с получателями и со всем остальным. Даже в этом случае не все будет гладко. У WebView возникают большие проблемы, когда он пытается определить свой размер внутри ScrollView.
Ответ: Можно попробовать отрисовать означенную часть UI внутри WebView.
Вопрос: То есть полностью перенести всю логику из нативной части в WebView и оставить эти контейнеры?
Ответ: Даже, может быть, логику переносить не надо, имеется в виду инжектирование Java-классов. Логику можно оставить и вызывать через инжектированный класс. В WebView можно перенести только UI.
Вопрос: Вы упоминали про игры в мессенджере. Они представляют собой web-приложения?
Ответ: Да, это web-страницы с JavaScript внутри WebView.
Вопрос: Вы все это делаете, чтобы просто не переписывать игры нативно?
Ответ: И для этого тоже. Но основная идея в том, чтобы дать сторонним разработчикам возможность создавать приложения, которые могут встраиваться в ICQ, и с помощью этого ICQ Web API взаимодействовать с мессенджером.
Вопрос: То есть в эти игры можно играть также через web-браузер на лэптопе?
Ответ: Да. Она может быть открыта в web-браузере, и мы иногда их прямо в нем и отлаживаем.
Вопрос: А если Intent, допустим, в Chrome прокинуть эту игрушку, какие проблемы тогда будут? Если не свою WebView писать, а воспользоваться услугами?
Ответ: Проблема в том, что в своем WebView мы можем предоставить API через инжектирование Java-класса, и с помощью этого API приложение сможет напрямую взаимодействовать с ICQ, отправлять ему различные команды. Допустим, команду на получение имени пользователя, на получение чатов, которые у него открыты, отправлять сообщения в чат непосредственно из ICQ. То есть из Chrome отправлять сообщения непосредственно в ICQ не получится. В нашем случае все это возможно.
Вопрос: Вы упомянули, что режете данные на куски по одному мегабайту. Как вы их потом собираете?
Ответ: Мы сейчас этого не делаем, потому что у нас нет такой потребности.
Вопрос: Хватает одного мегабайта?
Ответ: Да. Если картинки больше, то пытаемся их ужимать. Я сказал о том, что если такая потребность существует, то это может быть решением — разрезать и собирать потом в Java.
Вопрос: Как вы обеспечиваете безопасность работы приложений в песочнице? Правильно ли я понял, что из JavaScript приложения нужно вызывать инжектированные Java-классы?
Ответ: Да.
Вопрос: Как будет обеспечиваться в этом случае безопасность, запрещен ли доступ к каким-то системным функциям?
Ответ: Прямо сейчас, так как система еще довольно молодая, у нас в основном используются наши собственные web-приложения, и мы им полностью доверяем. В дальнейшем все приложения, которые будут поступать к нам, будут администрироваться, код будет просматриваться, для этого выделена специальная Security Team. Дополнительно будет создана специальная система разрешений, без которых приложения не смогут получить доступ к какой-то критической для пользователя информации.
Плюсы и минусы WebView
Прежде всего мы собрали плюсы и минусы использования WebView, чтобы на их основании выработать обоснованные рекомендации. Часть плюсов и минусов вызваны особенностью этой технологии, а часть связаны с нашим положением вещей.
Начнём с плюсов, с ними проще разобраться.
Сокращение общих затрат на разработку и TTM
Здесь всё понятно, при создании экрана на WebView не нужно с нуля реализовывать экран на всех трёх платформах Android, iOS и Web. В этом разрезе сокращение происходит за счёт переиспользования единой логики мобильного сайта в мобильных приложениях.
Конечно, прирост в скорости значительный, но он всё же будет не в 3 раза, как могло бы показаться, потому что всё равно требуется время на адаптацию вёрстки под платформы и на поддержку со стороны натива.
Синхронный Update на пользователей
Давайте рассмотрим случай, когда речь не про разработку полностью нового экрана, а про изменения в работе существующей фичи, реализованной на WebView. В этом случае очень велика вероятность, что для раскатки даже не потребуется обновления версии приложения, и пользователи старых версий приложения получат доступ к последним изменениям экрана. Причём произойти это может одновременно для всех платформ. Это актуально и для правок багов, ведь пользователи быстрее получат фиксы.
Возможности для команд без нативных разработчиков
У нас есть продуктовые команды, у которых нет нативных мобильных разработчиков iOS и Android. WebView даёт возможность таким командам внедрять фичи в мобильные приложения.
Однако здесь есть нюанс, что поддержка WebView всё равно требуется со стороны нативной разработки, как минимум в реализации переходов и передачи данных. Кроме того, таким командам приходится помнить, что они могут загнать самих себя в рамки, так как область применения WebView ограничена, но об этом ниже.
Вот мы и подобрались к минусам. Часть из них можно устранить или минимизировать, однако адаптировать WebView под 2 другие нативные платформы, которые сами по себе друг от друга заметно отличаются — задача очень дорогая, и мы всё равно будем сталкиваться с ограничениями и артефактами WebView, которых в принципе не может быть в нативе.
В оптимизацию WebView тоже нельзя без конца упарываться, поскольку мы всё же хотели достичь прироста в скорости 🙂
Не работает офлайн режим
Чисто технически WebView может отображать веб-страницы как с удаленного сайта, так и те, что уже храняться локально, например, в ресурсах приложения. Но такой способ мы не используем, так как в этом случае мы потеряем один их самых важных для нас плюсов WebView 2. Синхронный Update на пользователей. Уже не будет той гибкости, мы снова упремся в релизный цикл. А для нас это означает: чтобы отобразить UI, требуется получить данные из сети.
Понятно, что приложение у нас клиент-серверное, что большая часть нативных экранов всё равно не автономны без сети. Несмотря на это, есть много примеров, когда нативному экрану не требуется никакой загрузки данных. В этих случаях пользователь не всегда сможет довести свой флоу до конца, но он сможет понять, правильно ли он вообще сюда перешёл, заполнить свои данные до появления интернета, не потеряв их. Экран авторизации — пример такого экрана. C WebView мы в лучшем случае сможем показать нативную ошибку без загрузки минимальных данных.
В принципе можно было бы подумать в сторону дополнительного инструмента кэширования, чтобы обеспечить частичную поддержку офлайн-режима и для WebView. Но полностью достичь нативных возможностей всё равно не выйдет. Чтобы закэшировать что-то ненужное, нужно сначала загрузить что-то ненужное 🙂
Проблемы с локальным хранением данных
У WebView нет доступа к таким данным. Технически есть возможность прокидывать данные между WebView и нативной частью с целью сохранения и загрузки, однако это требует реализации на нативной стороне, что явно снижает профит от его использования в тех кейсах, где необходима подобная работа с данными.
Что касается хранилища самого WebView, то оно во власти операционной системы, логика его очистки различается в разных версиях, зависит от многих факторов, в том числе от свободного объема дискового пространства, времени последнего взаимодействия пользователя и т. п. Повлиять на это нет возможности. Получается, что WebView своими внутренними средствами не может гарантировать хранение важных данных.
Продолжительность загрузки
Экран в WebView долго загружает свой контент. Как минимум при первом переходе на него требуется гораздо больше времени, чем для нативного, и при слабом интернете разница только увеличивается. Очевидно, это накладывает дополнительные неудобства для пользователя. Помимо скорости, не стоит также забывать про объём передаваемой информации, как правило, WebView значительно прожорливей до пользовательского трафика.
Этот фактор накладывает дополнительные ограничения в решении проблем с навигацией, мы не можем на каждом этапе флоу создавать новый WebView.
Неконсистентность дизайна
У нас в проекте используется дизайн-система. Получается, на разных платформах: iOS, Android и Web, разная реализация. Фича на WebView будет использовать реализацию дизайн-системы для веба, а не нативную (iOS или Android), и она всегда будет отличаться от нативной даже в версиях одинаковой актуальности. Не будем сейчас глубоко вдаваться в детали нашей дизайн-системы, это не тема данной статьи. Отметим только, что у нас нет цели полностью унифицировать её реализации на разных платформах. Одна из причин в том, что в разных платформах разные UI/UX-паттерны, которые формируют у пользователей свои привычки, что тоже не хотелось бы нарушать.
Работу с этим минусом сильно усложняет пункт из плюсов: «2. Синхронный Update на пользователей». Вот ведь ирония! ) Получается, если фича внутри WebView будет одновременно обновляться без релиза приложения, изменения будут доступны и пользователям старых версий приложений. Нативный UI в них будет «законсервирован», использовать версию дизайн-системы, актуальную на момент релиза, в то время как для версии внутри WebView могут произойти сильные изменения, совершенно неконтролируемые с нативной стороны.
Различия в дизайне экранов будут чувствоваться и негативно влиять на общее впечатление от продукта.
С другой стороны, чем больше ресурсов вкладывать в поддержание консистентности, тем меньше будет профита в WebView в вопросе сокращения затрат на разработку.
Несовершенства UI
Можно несколько загримировать WebView под натив, но полностью всё же не получится. Всё равно останутся артефакты WebView, приводящие к багам, которых в нативных экранах не может быть в принципе.
В WebView отсутствует нативная плавность взаимодействия с интерфейсом экрана. Это касается и плавности скролла с анимациями, на нативе добиться 60 FPS намного проще, к тому же есть немало устройств с частотой обновления экрана 120 Гц, особенно Android.
Кроме того, отличается взаимодействие с его интерактивными элементами, что приводит к непривычному в контексте нативного приложения UX для пользователей. К примерам такого поведения внутри WebView можно добавить выделение текста с появляющимся контекстным меню:
Это просто пример, конкретно этот кейс может вылечить разработчик фичи, локальное решение довольно простое, но тогда об этом кейсе нужно помнить каждому фронтовому разработчику. Системное же решение надо прорабатывать.
Проблемы с навигацией
При использовании WebView навигация тоже накладывает свои трудности, особенно если фича WebView содержит переходы. В этом случае полностью не получится повторить нативный UX навигации, где кнопка «Назад» в NavBar ведёт к предыдущему экрану, как и жест возврата на предыдущий экран. Это ещё не все нативные возможности, скажем, в iOS есть нативное меню, которое всплывает при длительном нажатии на кнопку «Назад»: оно позволяет увидеть весь стек предыдущих экранов с возвратом к любому из них:
Помимо проблем с интеграцией WebView в нативную навигацию, имеется чисто фронтовая проблема построения навигации внутри WebView, если в ней требуются переходы. Универсального решения навигации на вебе, подходящего для наших мобильных приложений, нет. Нюансов становится больше, когда требуется кастомизация навигации, даже самая простая, как, например, запрет пользователю уйти из целевой фичи.
Порой это может приводить к очень спорным решениям, ломающим пользовательские шаблоны:
Здесь мы видим кнопку в нативном навбаре, закрывающую весь флоу WebView, и одновременно кнопку «Назад» внутри WebView, которая возвращает на предыдущий шаг внутри неё.
Невозможность комбинирования технологий на одном экране
Как нативный блок нельзя встроить внутрь экрана на WebView, так и WebView не стоит встраивать в нативный экран.
WebView — тяжёлый для отрисовки элемент, и в случае отдельного встроенного блока бьёт по производительности всего экрана. Когда это блок внутри нативного экрана, куда сильнее бросаются в глаза различия в поведении из пункта «5. Несовершенства UI».
Кроме того, WebView может спятисотить и не загрузиться. Такой случай обязательно нужно обрабатывать на нативной стороне, чтобы пользователь не столкнулся с пустым блоком.
Техническая возможность встроить WebView в нативный экран есть, это да. Однако на практике, если WebView будет блоком внутри нативного экрана, то он не сможет подстраивать свои размеры без дёрганья интерфейса. Также в этом случае его надо каким-то образом расположить среди остальных элементов, для этого необходимо понять, сколько места надо WebView при заданных ограничениях на ширину и/или высоту, ведь любой блок на экране должен занимать столько места, сколько ему нужно, не больше и не меньше. В принципе «достать» размеры, необходимые для отображения контента WebView, возможно через JS-функцию. Однако здесь сказывается факт, что отрисовка WebView работает медленнее, чем нативная, и придётся менять её размеры по колбэку, при этом, визуально экран будет дергаться. Другой вариант не лучше — ждать вычисление размеров всех блоков, что задержит отображение всей страницы, её TTI.
В iOS есть дополнительная проблема. Наше приложение Циан поддерживает iPad и перевороты устройства, при которых экран не пересоздаётся (в отличии от Android), а динамически меняет свою вёрстку. Помимо разворота, наше приложение также поддерживает режим многозадачность в iPad, при котором пользователь может разделить экран на две части, чтобы одновременно пользоваться двумя приложениями.
Исходя из всего этого, WebView стоит использовать только как Full-Screen экран, а не как блок.
Это накладывает сильные ограничения в области применения WebView в нашем проекте. Самые ключевые экраны у нас являются нативными, и менять это не планируется.
Не является полным реюзом мобильного сайта
Нельзя просто так взять и переиспользовать страницу мобильного сайта 🙂 Требуется адаптировать верстку, UX и UI, а порой и навигацию под мобильное приложение.
На сайте у нас два типа верстки: мобильная и планшетная, выбор зависит от ширины экрана. Дополнительная трудность здесь заключается в том, что размер экрана, выделенного для приложения в iPad, величина непостоянная, она меняется динамически при разворотах устройства и при переходе в режим многозадачности (когда экран разделяется на 2 части). Одно вращение может превратить ширину из планшетной в мобильную, и экран должен в этот момент перестроиться динамически, ведь в iOS при смене ориентации экраны не пересоздаются. На сайте у нас это место является проблемным.
Кроме того, необходимо убрать все ненужные переходы на другие фичи и навигацию по сайту, чтобы сделать пользователя мобильного приложения пользователем сайта внутри мобильного приложения.
Существуют дополнительные ограничения и нюансы в работе на разных платформах. Из известных, например, в WebView на Android не работают некоторые html5 теги и не работает DatePicker.
Поэтому не стоит думать, что использование WebView даёт тройной прирост в скорости.
Не работает фоновая активность
В отличии от натива, в механизме WebView нет возможности поднять в фоне сервис для получения или синхронизации данных.
У нас в приложении, например, с разрешения пользователя, происходит отслеживание изменений его местоположения, даже когда приложение не запущено, данные сохраняются локально и по мере накопления отправляются на наш сервер. Конечно, это сделано нативными средствами.
Невозможность использования всех возможностей платформы
При использовании WebView можно столкнуться с невозможностью использования сенсоров и датчиков мобильного устройства, доступных в нативе, таких как fingerprint, nfc, акселерометр, гироскоп и т. п.
В Android также невозможно или очень затруднительно из WebView взаимодействовать с другими нативными приложениями (в iOS это и в нативе практически невозможно).
Проблемы интеграции и версионирования
Кроме того, такое преимущество WebView как 2. Синхронный Update на пользователей, приводит к дополнительным рискам, когда WebView требуется обмен данными с нативной частью приложения.
В этом случае может произойти поломка самой интеграции в нативное приложение, когда что-то ломается в самой логике обмена данными. В наших чисто нативных фичах такой риск значительно ниже, так как их обновления привязаны к релизному циклу, нет проблем версионирования, и потенциальная поломка должна быть предотвращена прогоном тестов еще до релиза. В случае же с WebView логика может измениться в любой момент и существует несколько вариантов поломки:
Поэтому с соблюдением контракта требуется повышенное внимание, цена ошибки может оказаться высока, особенно опасен пункт 2, ведь его гораздо сложнее, прежде всего, поймать, а также воспроизвести и починить.
Проблемы с UI-тестами
UI-тесты у нас интеграционные, и проверяют работу всего пользовательского флоу, а не отдельно взятого экрана. Подробнее про UI-тесты в Циан мы писали в отдельной статье.
Даже когда WebView встроен отдельным экраном, могут возникать трудности с покрытием функционала UI-тестами, особенно в следующих случаях:
При отсутствии этих условий проблем с интеграционными UI-тестами в мобильных приложениях меньше: достаточно лишь проверить факт открытия экрана, а остальные проверки производить тестами на стороне Web.
Если же хотя бы одно из этих условий применимо, для такой фичи уже недостаточно прогона UI-тестов перед обновлением приложения, так как WebView не привязан к релизному циклу (ещё один привет плюсу 2) и поломка может произойти в любой момент. Значит, если функционал фичи на WebView важен (особенно если экран WebView не тупиковый), требуется прогонять её интеграционные тесты как можно чаще.
При этом дополнительные трудности возникают с их стабильностью, ведь любая проблема с интернет-соединением может привести к падению теста. В случае нативных фичей мы сводим end-to-end тесты к минимуму, заменяя реальные ответы на запросы заглушками. С WebView же этот номер так просто не пройдёт, ведь он не способен к автономной работе без сети. Соответственно, если WebView не является тупиковым во флоу пользователя, то для такой фичи требуется решение, как грамотно пройти этот шаг флоу (или сымитировать это) в прогоне UI-теста соседних экранов, чтобы одновременно и повысить стабильность теста, и не сделать его бессмысленным. Требуется чётко проработать, что и как должно тестироваться на стороне Web, что на стороне натива, как протестировать интеграцию.
Рекомендации по использованию
Если охарактеризовать одним предложением рассмотренные выше тезисы, то можно с уверенностью сказать, что инструмент WebView довольно ограничен и НЕ может дать качества нативной разработки.
Мы детально рассмотрели плюсы, минусы и ограничения WebView, и теперь, на основе технических ограничений, нюансов работы и опыта использования WebView в Циан, можно перейти рекомендациям, которые мы составили для своих продуктовых команд. Вот мы и подошли к нашему ответу на вопрос: «В каких случаях использование WebView уместно, а когда лучше разрабатывать нативно?».
Когда использование WebView может быть уместно
У вас MVP фича или эксперимент, в рамках которого хочется проверить гипотезу, не затратив при этом много ресурсов.
Здесь всё понятно, в данном случае большая часть вытекающих от использования WebView недоработок не являются критичными. На старте проекта следует лишь внимательно посмотреть, позволяют ли ограничения инструмента реализовать проект даже на уровне MVP.
При завершении эксперимента на WebView следует осознанно принять решение о дальнейшем способе реализации, желательно, в сторону натива.
Техническая невозможность реализовать иначе
В некоторых фичах требуется использовать сторонние технологии или сервисы, реализованные только для браузера и не имеющие нативного SDK. В этом случае стоит провести ресёрч, есть ли подходящие аналоги. Если аналогов нет, то и выбора нет — придётся делать на WebView.
В этом случае нужно уделить особое внимание флоу при проработке фичи, чтобы максимально аккуратно интегрировать такую фичу в приложение.
Фича максимально обособлена
Что вкладывается в слово «обособлена»?
Фича имеет и в будущем будет продолжать иметь отдельное логическое ответвление функционала, который линеен, конечен и никак не пересекается с нативной реализацией других фичей в приложении:
Простая задача
Когда у фичи простое назначение, от пользователя не требуется совершить в рамках флоу много действий, то пользователи будут испытывать меньше неудобств от WebView.
Такая фича представляет из себя простой лендинг или функционал, связанный только с отображением информации, не обладает большим количеством интерактива и переходов.
Это может быть какое-либо соглашение или промостраница.
Когда НЕ стоит использовать WebView
Если эксперименты прошли успешно, вы в фиче не сомневаетесь, уверены в том, что она в приложении на долгий срок и будет только продолжать развиваться.
Техническая невозможность сделать на WebView
Здесь всё просто, мы рассмотрели ограничения и минусы, если фиче требуется возможности, которых нет у WebView, то и WebView для неё не подходит.
Обратите внимание на пункты 2, 9, 10 в списке минусов.
Целевой функционал
WebView, к сожалению, не может дать качества натива и рассмотренные минусы и ограничения будут чувствоваться острее в фичах, на функционал которых мы целенаправленно ведём пользователя. Чем чаще пользователям предстоит иметь дело с экраном, тем меньше неудобств это должно им доставлять.
Минусы мы постарались подробно рассмотреть выше.
Фича не обособлена
Когда фича тесно не в одностороннем порядке связана с другим функционалом приложения, к WebView лучше не прибегать.
Вот небольшой чек-лист для фичи, которая может называться обособленной:
Обратите внимание на пункты 6, 7, 11, 12 в списке минусов.
Необособленную фичу у нас в Циан обязательно нужно согласовать с командами, с функционалом которых они тесно связаны. Хотя связанные фичи у нас принято согласовывать независимо от её технологий 🙂
UI/UX с большим количеством интерактива
Чем больше действий требуется произвести пользователям в рамках функционала фичи, тем меньше подходит для этого WebView.
Чем больше интерактива на экране, тем сложнее для него будет загримировать WebView под натив. С чем предстоит столкнуться, мы постарались подробно рассмотреть в минусах выше.
Скажем, заполнять форму своими данными удобнее в интерфейсе с максимально привычными контролами (форма здесь просто самый очевидный пример, но не единственный).
Итоги
Мы понимаем, что каждый проект по-своему уникален, и все возможные условия не охватить в рамках одной статьи. Мы просто постарались рассказать о возможных проблемах и собрать рекомендации, которые, надеемся, помогут критически взглянуть на вопрос использования WebView с разных сторон и принять взвешенное решение. Время, которое будет потрачено на синхронизацию и проработку этого вопроса на старте проекта, несравнимо меньше времени, которое предстоит потратить на переделки в случае ошибки.
У нас в Циан эти рекомендации не являются строгим запретом, однако все команды стараются их придерживаться. Если команда понимает, что WebView – их вариант, то:
Так что живем дружно 🙂