Как открыть webview
Перейти к содержимому

Как открыть webview

  • автор:

WebView — создай свой браузер

Android позволяет создать собственное окно для просмотра веб-страниц или даже создать свой клон браузера при помощи элемента WebView. Сам элемент использует движок WebKit и имеет множество свойств и методов. Мы ограничимся базовым примером создания приложения, с помощью которого сможем просматривать страницы в интернете. В последних версиях используется движок от Chromium, но большой разницы в этом нет для простых задач.

Создадим новый проект MyBrowser и сразу заменим код в файле разметки res/layout/activity_main.xml:

Теперь откроем файл активности MainActivity.java и объявим компонент WebView, а также инициализируем его — включим поддержку JavaScript и укажем страницу для загрузки.

 // Kotlin class MainActivity : AppCompatActivity() < private lateinit var webView: WebView override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) webView = findViewById(R.id.webView) webView.webViewClient = MyWebViewClient() // включаем поддержку JavaScript webView.getSettings().setJavaScriptEnabled(true) // указываем страницу загрузки webView.loadUrl("http://developer.alexanderklimov.ru/android") >> 
// Java private WebView webView; public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = findViewById(R.id.webView); // включаем поддержку JavaScript webView.getSettings().setJavaScriptEnabled(true); // указываем страницу загрузки webView.loadUrl("http://developer.alexanderklimov.ru/android"); >

Так как приложение будет использовать интернет, необходимо установить разрешение на доступ к интернету в файле-манифесте.

Там же в манифесте модифицируем строчку для экрана, удалив заголовок из нашего приложения (выделено жирным):

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

Страница в браузере

Чтобы решить данную проблему и открывать ссылки в своей программе, нужно переопределить класс WebViewClient и позволить нашему приложению обрабатывать ссылки. Добавим в коде вложенный класс:

 // Kotlin private class MyWebViewClient : WebViewClient() < @TargetApi(Build.VERSION_CODES.N) override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean < view.loadUrl(request.url.toString()) return true >// Для старых устройств override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean < view.loadUrl(url) return true >> 
// Java private class MyWebViewClient extends WebViewClient < @TargetApi(Build.VERSION_CODES.N) @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) < view.loadUrl(request.getUrl().toString()); return true; >// Для старых устройств @Override public boolean shouldOverrideUrlLoading(WebView view, String url) < view.loadUrl(url); return true; >>

Затем в методе onCreate() определим экземпляр MyWebViewClient. Он может находиться в любом месте после инициализации объекта WebView:

 // Kotlin webView.webViewClient = MyWebViewClient() 
// Java webView.setWebViewClient(new MyWebViewClient());

Теперь в нашем приложении создан WebViewClient, который позволяет загружать любой указанный URL, выбранный в WebView, в сам контейнер WebView, а не запускать браузер. За данную функциональность отвечает метод shouldOverrideUrlLoading(WebView, String), в котором мы указываем текущий WebView и нужный URL. Возвращаемое значение true говорит о том, что мы не нуждаемся в запуске стороннего браузера, а самостоятельно загрузим контент по ссылке. В версии API 24 добавили перегруженную версию метода, учитывайте это обстоятельство.

Повторно запустите программу и убедитесь, что ссылки загружаются теперь в самом приложении. Но теперь возникла ещё одна проблема. Мы не можем вернуться к предыдущей странице. Если мы нажмём на кнопку «BACK» (Назад) на устройстве, то просто закроем своё приложение. Для решения новой проблемы нам необходимо обрабатывать нажатие кнопки «BACK». Добавляем новый метод:

 // Kotlin override fun onBackPressed() < if (webView.canGoBack()) < webView.goBack() >else < super.onBackPressed() >> 
// Java @Override public void onBackPressed() < if(webView.canGoBack()) < webView.goBack(); >else < super.onBackPressed(); >>

Мы должны проверить, что WebView поддерживает навигацию на предыдущую страницу. Если условие верно, тогда вызывается метод goBack(), который возвращает нас на предыдущую страницу на один шаг назад. Если таких страниц набралось несколько, то мы можем последовательно вернуться к самой первой странице. При этом метод всегда будет возвращать значение true. Когда мы вернёмся на самую первую страницу, с которой начали путешествие по интернету, то вернётся значение false и обработкой нажатия кнопки BACK займётся уже сама система, которая закроет экран приложения.

Запустите приложение ещё раз. У вас появился свой собственный веб-браузер, позволяющий ходить по ссылкам и возвращаться на предыдущую страницу. Изучив документацию, вы можете оснастить приложение и другим вкусными плюшками для своего браузера.

WebView

Если вам нужно часть ссылок, ведущих на ваш сайт открывать в браузере, а локальные ссылки открывать в приложении, то применяйте условие с разными возвращаемыми значениями.

 public class MyWebViewClient extends WebViewClient < @Override public boolean shouldOverrideUrlLoading(WebView view, String url) < if(Uri.parse(url).getHost().endsWith("developer.alexanderklimov.ru")) < return false; >Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); view.getContext().startActivity(intent); return true; > > 

Универсальный метод, который все локальные ссылки откроет в приложении, остальные в браузере (меняем одну строчку):

 public class MyAppWebViewClient extends WebViewClient < @Override public boolean shouldOverrideUrlLoading(WebView view, String url) < if(Uri.parse(url).getHost().length() == 0) < return false; >Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); view.getContext().startActivity(intent); return true; > > 

А сейчас немного усложним пример, чтобы у пользователя появилась альтернатива стандартным браузерам.

Чтобы было понятнее, переделаем пример следующим образом. Создайте две активности. На первой активности разместите кнопку для перехода на вторую активность, а на второй активности разместите компонент WebView.

В манифесте прописываем у второй активности фильтр.

          

Код для кнопки для перехода на вторую активность.

 public void onClick(View view) < Intent intent = new Intent("ru.alexanderklimov.Browser"); intent.setData(Uri.parse("http://developer.alexanderklimov.ru/android/")); startActivity(intent); >

Мы создали собственное намерение с указанием фильтра и предоставили данные — адрес сайта.

Вторая активность должна принять данные:

 package ru.alexanderklimov.testapplication; import android.net.Uri; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.webkit.WebView; import android.webkit.WebViewClient; public class SecondActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Uri url = getIntent().getData(); WebView webView = findViewById(R.id.webView); webView.setWebViewClient(new Callback()); webView.loadUrl(url.toString()); > private class Callback extends WebViewClient < @Override public boolean shouldOverrideUrlLoading (WebView view, String url) < return(false); >> > 

В фильтре для второй активности мы указали два действия.

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

Запустите второе приложение (первое приложение можно закрыть) и нажмите на кнопку. У вас запустится не первое приложение с начальным экраном, а сразу вторая активность с мини-браузером. Таким образом, любое приложение может запустить браузер, не зная имени класса вашей активности, а используя только строку «ru.alexanderklimov.Browser», передаваемую в Intent. При этом ваша активность с браузером должна иметь категорию по умолчанию и данные. Напомню:

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

 public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW"; 

Перепишем код для кнопки у второго приложения

 Intent(android.content.Intent.ACTION_VIEW, Uri.parse("http://developer.alexanderklimov.ru/android/")); startActivity(intent); 

Что произойдёт на этот раз? Мы помним, что у нас прописано два действия, включая и android.intent.action.VIEW. А значит наше первое приложение с браузером тоже должно распознавать эту команду, когда какое-то приложение у пользователя использует этот код. На эмуляторе как минимум есть одна такая программа «Browser», и теперь к ней добавилась наша вторая активность из первого приложения. На экране появится выбор из двух приложений.

А если удалить все альтернативные браузеры и оставить только вашу программу, то и выбора не будет. Ваш браузер станет основным. И если какое-то приложение захочет запустить веб-страницу указанным способом, то откроется ваша программа.

Небольшое замечание. Если заменить последнюю строчку на такую:

 startActivity(Intent.createChooser(intent, "Мяу. ")); 

То в окне выбора программы вместо верхней строки «Open with» или её локального перевода появится ваша строка. Но не это главное. Если по каким-то причинам на устройстве не окажется ни одного браузера, то данный вариант кода не вызовет краха приложения, в отличие от первоначального варианта. Поэтому используйте предложенный вариант ради надёжности.

Относительно недавно увидел новый способ открытия ссылок вне браузера. Необходимо использовать флаг FLAG_ACTIVITY_REQUIRE_NON_BROWSER.

 val url = "http://developer.alexanderklimov.ru" val intent = Intent(ACTION_VIEW, Uri.parse(url)).apply

WebView

WebView — это компонент, который позволяет встраивать веб-страницы в приложения, своеобразный мини-браузер. Находится в разделе Containers.

В старых версиях Android WebView использовал движок WebKit. В Android 4.4 он стал использовать движок Chromium или Blink. В Android 5 появилось отдельное приложение System WebView, которое можно скачать из Play Market. Такое разделение позволило обновлять движок без обновления системы. На этом приключения не закончились. В Android 7.0 уже используется движок Chrome, а если этого браузера на устройстве нет, то используется System WebView. Подобные выкрутасы не прошли даром, программисты часто жалуются, что какой-то кусок кода не работает. Приходится проверять работу на разных устройствах.

Надеюсь, вы уже познакомились с базовым примером по созданию собственного браузера. Рассмотрим дополнительные возможности элемента WebView.

Загружаем локальные страницы и картинки

Если вы хотите загружать в WebView страницы не из интернета, а со своего приложения, то разместите нужные файлы в папке assets, например, assets/mypage.html. Доступ к файлу вы можете получить через конструкцию file://android_asset:

 webView = findViewById(R.id.mybrowser); webView.loadUrl("file:///android_asset/mypage.html"); 

Аналогично поступаем с картинками, которые встречаются в html-файле

   

Также можно загрузить файл из папки res/raw:

 webView.loadUrl("file:///android_res/raw/cat.html"); 

Если картинка находится на внешнем накопителе, то попробуйте вариант:

 WebView webView = findViewById(R.id.webView); String imageName = "cutecat.png"; String catUrl = "file://" + Environment.getExternalStorageDirectory().getAbsolutePath() .toString() + "/" + imageName; webView.loadUrl(catUrl); 

Недавно наткнулся на фрагмент кода, где нужно добавить несколько новых настроек для работы с файлами. Пример для Kotlin.

 val webView = findViewById(R.id.webView) // Enable the WebView to access content through file: URLs webView.settings.apply

Загружаем данные при помощи loadData() и loadDataWithBaseURL()

Данные можно загрузить с помощью метода loadData():

 String htmlText = "Percent test: 100% "; WebView webView = findViewById(R.id.webView); webView.loadData(htmlText, "text/html", "en_US"); 

Если текст простой, то этот способ подойдёт. Но в данном примере встречается символ процента, который относится к спецсимволам и часть текста может оказаться недоступной. Если в тексте встречаются подобные символы, то лучше использовать метод loadDataWithBaseURL():

 webView.loadDataWithBaseURL(null, htmlText, "text/html", "en_US", null); 

Если вам приходится использовать loadData(), то спецсимволы можно заменить при помощи метода replace():

 String webData = stringBuffer.toString(); // поступающие данные webData = webData.replace("#", "%23"); webData = webData.replace("%", "%25"); webData = webData.replace("\\", "%27"); webData = webData.replace("?", "%3f"); webView.loadData(webData, "text/html", "UTF-8"); 

Проблемы с кодировкой

У меня есть программа в Google Play, использующая WebView. К моему удивлению, некоторые пользователи жаловались, что текст нечитаем, так как они видят только кракозябры. Особенно много жалоб было от пользователей с планшетами. Оказалось, что проблема довольна распространённая и обсуждается на форумах. Танцы с бубнами (установка явной кодировки UTF-8) не помогают. Нашёл один ответ, который у некоторых заработал, на всякий случай я его здесь оставлю.

 // перед загрузкой данных (load. ) WebSettings settings = mWebView.getSettings(); settings.setDefaultTextEncodingName("utf-8"); 

Но я рекомендую просто использовать метод loadDataWithBaseURL(). Работает стабильно.

Методы

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

  • reload()
  • goForward()
  • goBack()

Используем зум для просмотра

Не забывайте, что WebView можно использовать не только для просмотра html-страниц, но и для просмотра изображений. Поэтому данный компонент вполне можно использовать просмотра картинок с котиками, к тому же вы можете включить встроенный механизм масштабирования:

При работе с протоколом http используйте совет Cleartext HTTP traffic not permitted (https)

 mWebView = findViewById(R.id.webView1); // устанавливаем Zoom control mWebView.getSettings().setBuiltInZoomControls(true); // загружаем картинку (не забудьте установить разрешение на интернет) mWebView.loadUrl("http://netsources.narod.ru/friday/alkocat.jpg"); this.setTitle("WebView"); 

Zoom

Прозрачность

Устанавливать прозрачность лучше программно. Встречал жалобы, что через XML это свойство не работает.

 webView.setBackgroundColor(0x00000000); 

WebView в Lollipop

В Android 5.0 компонент доступен в Google Play (Android System WebView) и его можно обновлять на устройстве.

Компонент теперь основывается на движке Chromium и поддерживает следующие новинки.

Можно ознакомиться с некоторыми примерами — GoogleChrome/chromium-webview-samples. Там есть примеры с WebRTC, полноэкранным режимом, касаниями экрана, выбора файла, работой с JavaScript-сценариями.

Кроме того, стал доступен Safe Browsing — механизм, предупреждающий об опасных ссылках. Включается через манифест.

Советы

Фон

Если вы заметили, что экран мерцает во время загрузки WebView, то поменяйте фон. Мерцание происходит из-за смены фона приложения (темы), на белый фон по умолчанию для WebView, а потом на фон, который прописан на странице.

 mWebView.setBackgroundColor(Color.parseColor("#3498db")); mWebView.setBackgroundColor(getResources().getColor(R.color.my_color_name)); // и т.п. 

Касания экрана

Так как поддерживаются касания экрана, то старайтесь использовать на веб-странице визуальные эффекты нажатия кнопок и других элементов при помощи псевдокласса :active, например, так:

 .btn < display: inline-block; position: relative; background-color: #f39c12; padding: 14px; border-radius: 5px; border-bottom-style: solid; border-width: 4px; border-color: #DA8300; >.btn:active

Настройки

В API 24 появилась возможность открыть окно настроек и выбрать движок для WebView:

 Intent intent = new Intent(Settings.ACTION_WEBVIEW_SETTINGS); if (intent.resolveActivity(getPackageManager()) != null)

Ночной режим

Появилась поддержка тёмной темы в последних версиях WebView.

 implementation "androidx.webkit:webkit:1.2.0-alpha01" 

За ночной режим отвечает класс WebViewFeature, который имеет в своём составе коллекцию различных возможностей. Проверить поддержку той или иной возможности можно через isFeatureSupported().

 // Поддерживается ли ночной режим if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK))

Всего три варианта для тёмной темы.

  • FORCE_DARK_OFF
  • FORCE_DARK_AUTO
  • FORCE_DARK_ON

Включаем ночной режим.

 WebSettingsCompat.setForceDark(webView.settings, WebSettingsCompat.FORCE_DARK_ON) 

Получаем текущий режим.

 val forceDarkMode = WebSettingsCompat.getForceDark(webView.settings) 

WebView как браузер по умолчанию или как открывать ссылки в своём браузере

введите сюда описание изображения

У меня есть простой браузер. Мне надо чтобы в нём открывались ссылки, как например в Google Chrome. Как это реализовать. Гуглил, нужного ответа не нашёл. Да и в манифесте всё что нужно прописал, но в менюшке он не алё. Помогите пожалуйста, если кто с этим сталкивался. Заранее спасибо

public class BrowserActivity extends AppCompatActivity < private SharedPreferences mSharedPreferences; private static final String TAG = BrowserActivity.class.getSimpleName(); private String mCM; private ValueCallbackmUM; private ValueCallback mUMA; private final static int FCR=1; SwipeRefreshLayout swipeRefreshLayout; ProgressBar progressBar; EditText inputUrl; WebView webView; ImageButton forward, back; @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent)< super.onActivityResult(requestCode, resultCode, intent); if(Build.VERSION.SDK_INT >= 21) < Uri[] results = null; if(resultCode== Activity.RESULT_OK)< if(requestCode == FCR)< if(null == mUMA)< return; >if(intent == null)< if(mCM != null)< results = new Uri[]; > >else< String dataString = intent.getDataString(); if(dataString != null)< results = new Uri[]; > > > > mUMA.onReceiveValue(results); mUMA = null; >else < if(requestCode == FCR)< if(null == mUM) return; Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); mUM.onReceiveValue(result); mUM = null; >> > @SuppressLint() @Override protected void onCreate(Bundle savedInstanceState) < setTheme(ThemeUtils.getCurrentTheme()); super.onCreate(savedInstanceState); setContentView(R.layout.activity_browser); if(Build.VERSION.SDK_INT >=23 && (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) < ActivityCompat.requestPermissions(BrowserActivity.this, new String[], 1); > progressBar = (ProgressBar) findViewById(R.id.progressBar); webView = (WebView) findViewById(R.id.webView); registerForContextMenu(webView); webView.getSettings().setBuiltInZoomControls(false); webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); webView.getSettings().setGeolocationDatabasePath(getFilesDir().getPath() ); mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swiperefresh); swipeRefreshLayout.setColorSchemeResources( R.color.colorAccent); webView.setWebViewClient(new WebKit()); webView.setWebChromeClient(new WebChromeClient() < @Override public void onProgressChanged(WebView view, int newProgress) < progressBar.setProgress(newProgress); if(newProgress==100) progressBar.setVisibility(View.GONE); else progressBar.setVisibility(View.VISIBLE); >@Override public void onPermissionRequest(final PermissionRequest request) < if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) < request.grant(request.getResources()); >> @Override public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) < callback.invoke(origin, true, false); >public boolean onShowFileChooser( WebView webView, ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) < if(mUMA != null)< mUMA.onReceiveValue(null); >mUMA = filePathCallback; Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if(takePictureIntent.resolveActivity(BrowserActivity.this.getPackageManager()) != null)< File photoFile = null; try< photoFile = createImageFile(); takePictureIntent.putExtra("PhotoPath", mCM); >catch(IOException ex) < Log.e(TAG, "Image file creation failed", ex); >if(photoFile != null)< mCM = "file:" + photoFile.getAbsolutePath(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); >else < takePictureIntent = null; >> Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); contentSelectionIntent.setType("*/*"); Intent[] intentArray; if(takePictureIntent != null)< intentArray = new Intent[]; >else < intentArray = new Intent[0]; >Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); chooserIntent.putExtra(Intent.EXTRA_TITLE, R.string.img_chooser); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); startActivityForResult(chooserIntent, FCR); return true; > public class Callback extends WebViewClient < public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)< Toast.makeText(getApplicationContext(), R.string.not_app, Toast.LENGTH_SHORT).show(); >> private File createImageFile() throws IOException < @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "img_"+timeStamp+"_"; File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); return File.createTempFile(imageFileName,".jpg",storageDir); >>); swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() < @Override public void onRefresh() < webView.reload(); >>); webView.setWebViewClient(new WebViewClient() < @Override public boolean shouldOverrideUrlLoading(WebView view, String url) < if( URLUtil.isNetworkUrl(url) ) < return false; >if (appInstalledOrNot(url)) < Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity( intent ); >else < >return true; > private boolean appInstalledOrNot(String uri) < PackageManager pm = getPackageManager(); try < pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); return true; >catch (PackageManager.NameNotFoundException e) < >return false; > @Override public void onPageFinished(WebView view, String url) < swipeRefreshLayout.setRefreshing(false); super.onPageFinished(view, url); >>); WebSettings webset = webView.getSettings(); webset.setJavaScriptEnabled(true); webset.setAllowFileAccess(true); if(Build.VERSION.SDK_INT >= 21)< webset.setMixedContentMode(0); webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); >else if(Build.VERSION.SDK_INT >= 19)< webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); >else if(Build.VERSION.SDK_INT < 19)< webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); >CookieManager.getInstance().setAcceptCookie(false); webView = findViewById(R.id.webView); Intent intent = getIntent(); String search_bar = intent.getStringExtra("search_b"); webView.setBackgroundColor(Color.TRANSPARENT); webView.getSettings().setLoadsImagesAutomatically(true); webView.clearFormData(); webView.getSettings().setSavePassword(true); webView.getSettings().setSaveFormData(true); webView.getSettings().setCacheMode( WebSettings.LOAD_DEFAULT ); webView.clearCache(false); if ( !isNetworkAvailable() ) < webView.getSettings().setCacheMode( WebSettings.LOAD_CACHE_ELSE_NETWORK ); >webView.loadUrl("https://duckduckgo.com/?q=" + search_bar); webView.setDownloadListener(new DownloadListener() < @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) < DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); request.setMimeType(mimeType); String cookies = CookieManager.getInstance().getCookie(url); request.addRequestHeader("cookie", cookies); request.addRequestHeader("User-Agent", userAgent); request.setDescription("Download file"); request.setTitle(URLUtil.guessFileName(url, contentDisposition, mimeType)); request.allowScanningByMediaScanner(); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, contentDisposition, mimeType)); DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); dm.enqueue(request); Toast.makeText(getApplicationContext(), R.string.download_toast, Toast.LENGTH_LONG).show(); >>); > private boolean isNetworkAvailable() < ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService( CONNECTIVITY_SERVICE ); NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); return activeNetworkInfo != null && activeNetworkInfo.isConnected(); >public void onBackPressed() < if(webView.canGoBack())< webView.goBack(); >else < super.onBackPressed(); >> @Override protected void onResume() < super.onResume(); Boolean password = mSharedPreferences.getBoolean(getString(R.string.pass_t), false); Boolean cookies = mSharedPreferences.getBoolean(getString(R.string.cookie_t), true); Boolean cacheWeb = mSharedPreferences.getBoolean(getString(R.string.cache_t), true); Boolean fontLarge = mSharedPreferences.getBoolean(getString(R.string.font_t), false); Boolean supportJavaScript = mSharedPreferences.getBoolean(getString(R.string.js_t), true); Boolean navButtons = mSharedPreferences.getBoolean(getString(R.string.nav_but_t), false); if (password) < webView.clearFormData(); webView.getSettings().setSavePassword(false); webView.getSettings().setSaveFormData(false); >if (cookies) < CookieManager.getInstance().setAcceptCookie(true); >if (cacheWeb) < webView.getSettings().setCacheMode( WebSettings.LOAD_NO_CACHE); webView.clearHistory(); webView.clearCache(true); if ( !isNetworkAvailable() ) < webView.getSettings().setCacheMode( WebSettings.LOAD_NO_CACHE ); >> webView.getSettings().setJavaScriptEnabled(supportJavaScript); webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(supportJavaScript); if (fontLarge) < webView.getSettings().setDefaultFontSize(20); >if (navButtons) < showNavButtons(); >else < hideNavButtons(); >> void hideNavButtons() < ImageButton mWebViewBackButton = (ImageButton) findViewById(R.id.mWebViewBackButton); ImageButton mWebViewForwardButton = (ImageButton) findViewById(R.id.mWebViewForwardButton); mWebViewBackButton.setVisibility(View.GONE); mWebViewForwardButton.setVisibility(View.GONE); >void showNavButtons() < ImageButton mWebViewBackButton = (ImageButton) findViewById(R.id.mWebViewBackButton); ImageButton mWebViewForwardButton = (ImageButton) findViewById(R.id.mWebViewForwardButton); mWebViewBackButton.setVisibility(View.VISIBLE); mWebViewForwardButton.setVisibility(View.VISIBLE); >public void WebViewGoForward(View view) < if (webView.isFocused() && webView.canGoForward()) < webView.goForward(); >> public void WebViewGoBack(View view) < if (webView.isFocused() && webView.canGoBack()) < webView.goBack(); >else < super.onBackPressed(); finish(); >> > 

WebView: забыть нельзя интегрировать

При разработке мобильного приложения iOS или Android рано или поздно может встать вопрос: «Реализовать фичу на WebView или же нативно?». В некоторых случаях ответ лежит на поверхности, но, к сожалению, так бывает не всегда. А если очень велик соблазн предоставить пользователям новый функционал поскорее — это может склонить к неправильному решению, с которым впоследствии предстоит что-то сделать.

Сегодня мы хотим поделиться с вами тем, какую стратегию мы выбрали в Циан для себя и как к ней пришли. Посмотрим, где же мы поставили запятую 🙂

Предыстория

У Циан есть мобильные приложения iOS, Android, ну и Web-сайт с адаптированной вёрсткой под разные типы устройств. У нас довольно большой департамент разработки, и он разделен на продуктовые направления, каждое из которых отвечает за свой функционал. Продуктовые направления максимально автономны и свободны в способах достижения своих бизнес-целей, в принятии решений, в выборе инструментов.

Но объединяет их одно — желание максимально быстро проверять свои гипотезы и максимальной скорости разработки фичей. В погоне за этим команды стали чаще прибегать к использованию WebView, особенно в рамках MVP-версий фичи. Порой возникали случаи, когда это промежуточное тестовое решение в нашем проекте несколько задерживалось 🙂

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

Наши рекомендации нисколько не претендуют на истину, мы их делали прежде всего для себя, исходя из наших условий: наша структура, требования бизнеса, наши проекты. Но вполне возможно, эти выработанные тезисы помогут вам выбрать технологии для фичи в вашем проекте. Если вы почерпнёте что-то новое для себя, что поможет добавить деталей вашей собственной стратегии использования WebView, мы будем особенно рады! )

На каждой из платформ у пользователя есть свои привычки и паттерны, поэтому, например, пользователю Android потребуется время, чтобы привыкнуть к iOS, и наоборот. Исходя из этого для нас важно во всех флоу максимально сохранять привычные пользователям каждой платформы основные UX-паттерны, такие как навигация, и поведения, поэтому в нашем случае WebView при использовании должен им соответствовать.

А теперь давайте разберёмся, к чему мы пришли. Поехали!

Плюсы и минусы WebView

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

Плюсы

Начнём с плюсов, с ними проще разобраться.

1. Сокращение общих затрат на разработку и TTM

Здесь всё понятно, при создании экрана на WebView не нужно с нуля реализовывать экран на всех трёх платформах Android, iOS и Web. В этом разрезе сокращение происходит за счёт переиспользования единой логики мобильного сайта в мобильных приложениях.

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

2. Синхронный Update на пользователей

Давайте рассмотрим случай, когда речь не про разработку полностью нового экрана, а про изменения в работе существующей фичи, реализованной на WebView. В этом случае очень велика вероятность, что для раскатки даже не потребуется обновления версии приложения, и пользователи старых версий приложения получат доступ к последним изменениям экрана. Причём произойти это может одновременно для всех платформ. Это актуально и для правок багов, ведь пользователи быстрее получат фиксы.

3. Возможности для команд без нативных разработчиков

У нас есть продуктовые команды, у которых нет нативных мобильных разработчиков iOS и Android. WebView даёт возможность таким командам внедрять фичи в мобильные приложения.

Однако здесь есть нюанс, что поддержка WebView всё равно требуется со стороны нативной разработки, как минимум в реализации переходов и передачи данных. Кроме того, таким командам приходится помнить, что они могут загнать самих себя в рамки, так как область применения WebView ограничена, но об этом ниже.

Минусы

Вот мы и подобрались к минусам. Часть из них можно устранить или минимизировать, однако адаптировать WebView под 2 другие нативные платформы, которые сами по себе друг от друга заметно отличаются — задача очень дорогая, и мы всё равно будем сталкиваться с ограничениями и артефактами WebView, которых в принципе не может быть в нативе.

В оптимизацию WebView тоже нельзя без конца упарываться, поскольку мы всё же хотели достичь прироста в скорости 🙂

1. Не работает офлайн режим

Чисто технически WebView может отображать веб-страницы как с удаленного сайта, так и те, что уже храняться локально, например, в ресурсах приложения. Но такой способ мы не используем, так как в этом случае мы потеряем один их самых важных для нас плюсов WebView 2. Синхронный Update на пользователей. Уже не будет той гибкости, мы снова упремся в релизный цикл. А для нас это означает: чтобы отобразить UI, требуется получить данные из сети.

Понятно, что приложение у нас клиент-серверное, что большая часть нативных экранов всё равно не автономны без сети. Несмотря на это, есть много примеров, когда нативному экрану не требуется никакой загрузки данных. В этих случаях пользователь не всегда сможет довести свой флоу до конца, но он сможет понять, правильно ли он вообще сюда перешёл, заполнить свои данные до появления интернета, не потеряв их. Экран авторизации — пример такого экрана. C WebView мы в лучшем случае сможем показать нативную ошибку без загрузки минимальных данных.

В принципе можно было бы подумать в сторону дополнительного инструмента кэширования, чтобы обеспечить частичную поддержку офлайн-режима и для WebView. Но полностью достичь нативных возможностей всё равно не выйдет. Чтобы закэшировать что-то ненужное, нужно сначала загрузить что-то ненужное 🙂

2. Проблемы с локальным хранением данных

У нас на проекте есть сквозные данные, которые не привязаны к какому-либо конкретному User Flow и могут использоваться несколькими несвязанными между собой экранами. Такие данные сохраняются локально на устройстве и обновляются лишь время от времени, вплоть до раза в неделю.

У WebView нет доступа к таким данным. Технически есть возможность прокидывать данные между WebView и нативной частью с целью сохранения и загрузки, однако это требует реализации на нативной стороне, что явно снижает профит от его использования в тех кейсах, где необходима подобная работа с данными.

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

3. Продолжительность загрузки

Экран в WebView долго загружает свой контент. Как минимум при первом переходе на него требуется гораздо больше времени, чем для нативного, и при слабом интернете разница только увеличивается. Очевидно, это накладывает дополнительные неудобства для пользователя. Помимо скорости, не стоит также забывать про объём передаваемой информации, как правило, WebView значительно прожорливей до пользовательского трафика.

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

4. Неконсистентность дизайна

У нас в проекте используется дизайн-система. Получается, на разных платформах: iOS, Android и Web, разная реализация. Фича на WebView будет использовать реализацию дизайн-системы для веба, а не нативную (iOS или Android), и она всегда будет отличаться от нативной даже в версиях одинаковой актуальности. Не будем сейчас глубоко вдаваться в детали нашей дизайн-системы, это не тема данной статьи. Отметим только, что у нас нет цели полностью унифицировать её реализации на разных платформах. Одна из причин в том, что в разных платформах разные UI/UX-паттерны, которые формируют у пользователей свои привычки, что тоже не хотелось бы нарушать.

Работу с этим минусом сильно усложняет пункт из плюсов: «2. Синхронный Update на пользователей». Вот ведь ирония! ) Получается, если фича внутри WebView будет одновременно обновляться без релиза приложения, изменения будут доступны и пользователям старых версий приложений. Нативный UI в них будет «законсервирован», использовать версию дизайн-системы, актуальную на момент релиза, в то время как для версии внутри WebView могут произойти сильные изменения, совершенно неконтролируемые с нативной стороны.

Различия в дизайне экранов будут чувствоваться и негативно влиять на общее впечатление от продукта.

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

5. Несовершенства UI

Можно несколько загримировать WebView под натив, но полностью всё же не получится. Всё равно останутся артефакты WebView, приводящие к багам, которых в нативных экранах не может быть в принципе.

В WebView отсутствует нативная плавность взаимодействия с интерфейсом экрана. Это касается и плавности скролла с анимациями, на нативе добиться 60 FPS намного проще, к тому же есть немало устройств с частотой обновления экрана 120 Гц, особенно Android.

Кроме того, отличается взаимодействие с его интерактивными элементами, что приводит к непривычному в контексте нативного приложения UX для пользователей. К примерам такого поведения внутри WebView можно добавить выделение текста с появляющимся контекстным меню:

Это просто пример, конкретно этот кейс может вылечить разработчик фичи, локальное решение довольно простое, но тогда об этом кейсе нужно помнить каждому фронтовому разработчику. Системное же решение надо прорабатывать.

6. Проблемы с навигацией

При использовании WebView навигация тоже накладывает свои трудности, особенно если фича WebView содержит переходы. В этом случае полностью не получится повторить нативный UX навигации, где кнопка «Назад» в NavBar ведёт к предыдущему экрану, как и жест возврата на предыдущий экран. Это ещё не все нативные возможности, скажем, в iOS есть нативное меню, которое всплывает при длительном нажатии на кнопку «Назад»: оно позволяет увидеть весь стек предыдущих экранов с возвратом к любому из них:

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

Порой это может приводить к очень спорным решениям, ломающим пользовательские шаблоны:

Здесь мы видим кнопку в нативном навбаре, закрывающую весь флоу WebView, и одновременно кнопку «Назад» внутри WebView, которая возвращает на предыдущий шаг внутри неё.

7. Невозможность комбинирования технологий на одном экране

Как нативный блок нельзя встроить внутрь экрана на WebView, так и WebView не стоит встраивать в нативный экран.

WebView — тяжёлый для отрисовки элемент, и в случае отдельного встроенного блока бьёт по производительности всего экрана. Когда это блок внутри нативного экрана, куда сильнее бросаются в глаза различия в поведении из пункта «5. Несовершенства UI».

Кроме того, WebView может спятисотить и не загрузиться. Такой случай обязательно нужно обрабатывать на нативной стороне, чтобы пользователь не столкнулся с пустым блоком.

Техническая возможность встроить WebView в нативный экран есть, это да. Однако на практике, если WebView будет блоком внутри нативного экрана, то он не сможет подстраивать свои размеры без дёрганья интерфейса. Также в этом случае его надо каким-то образом расположить среди остальных элементов, для этого необходимо понять, сколько места надо WebView при заданных ограничениях на ширину и/или высоту, ведь любой блок на экране должен занимать столько места, сколько ему нужно, не больше и не меньше. В принципе «достать» размеры, необходимые для отображения контента WebView, возможно через JS-функцию. Однако здесь сказывается факт, что отрисовка WebView работает медленнее, чем нативная, и придётся менять её размеры по колбэку, при этом, визуально экран будет дергаться. Другой вариант не лучше — ждать вычисление размеров всех блоков, что задержит отображение всей страницы, её TTI.

В iOS есть дополнительная проблема. Наше приложение Циан поддерживает iPad и перевороты устройства, при которых экран не пересоздаётся (в отличии от Android), а динамически меняет свою вёрстку. Помимо разворота, наше приложение также поддерживает режим многозадачность в iPad, при котором пользователь может разделить экран на две части, чтобы одновременно пользоваться двумя приложениями.

Исходя из всего этого, WebView стоит использовать только как Full-Screen экран, а не как блок.

Это накладывает сильные ограничения в области применения WebView в нашем проекте. Самые ключевые экраны у нас являются нативными, и менять это не планируется.

8. Не является полным реюзом мобильного сайта

Нельзя просто так взять и переиспользовать страницу мобильного сайта 🙂 Требуется адаптировать верстку, UX и UI, а порой и навигацию под мобильное приложение.

На сайте у нас два типа верстки: мобильная и планшетная, выбор зависит от ширины экрана. Дополнительная трудность здесь заключается в том, что размер экрана, выделенного для приложения в iPad, величина непостоянная, она меняется динамически при разворотах устройства и при переходе в режим многозадачности (когда экран разделяется на 2 части). Одно вращение может превратить ширину из планшетной в мобильную, и экран должен в этот момент перестроиться динамически, ведь в iOS при смене ориентации экраны не пересоздаются. На сайте у нас это место является проблемным.

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

Существуют дополнительные ограничения и нюансы в работе на разных платформах. Из известных, например, в WebView на Android не работают некоторые html5 теги и не работает DatePicker.

Поэтому не стоит думать, что использование WebView даёт тройной прирост в скорости.

9. Не работает фоновая активность

В отличии от натива, в механизме WebView нет возможности поднять в фоне сервис для получения или синхронизации данных.

У нас в приложении, например, с разрешения пользователя, происходит отслеживание изменений его местоположения, даже когда приложение не запущено, данные сохраняются локально и по мере накопления отправляются на наш сервер. Конечно, это сделано нативными средствами.

10. Невозможность использования всех возможностей платформы

При использовании WebView можно столкнуться с невозможностью использования сенсоров и датчиков мобильного устройства, доступных в нативе, таких как fingerprint, nfc, акселерометр, гироскоп и т. п.

В Android также невозможно или очень затруднительно из WebView взаимодействовать с другими нативными приложениями (в iOS это и в нативе практически невозможно).

11. Проблемы интеграции и версионирования

Кроме того, такое преимущество WebView как 2. Синхронный Update на пользователей, приводит к дополнительным рискам, когда WebView требуется обмен данными с нативной частью приложения.

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

  1. Последняя версия WebView — последняя версия приложения.
  2. Последняя версия WebView — одна/несколько предыдущих версий приложения (с последней версией приложения всё в порядке).
  3. Комбо предыдущих двух пунктов.

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

12. Проблемы с UI-тестами

UI-тесты у нас интеграционные, и проверяют работу всего пользовательского флоу, а не отдельно взятого экрана. Подробнее про UI-тесты в Циан мы писали в отдельной статье.

Даже когда WebView встроен отдельным экраном, могут возникать трудности с покрытием функционала UI-тестами, особенно в следующих случаях:

  • если WebView требуется обмен данными с нативной частью;
  • если экран WebView является не тупиковым в своем флоу.

При отсутствии этих условий проблем с интеграционными UI-тестами в мобильных приложениях меньше: достаточно лишь проверить факт открытия экрана, а остальные проверки производить тестами на стороне Web.

Если же хотя бы одно из этих условий применимо, для такой фичи уже недостаточно прогона UI-тестов перед обновлением приложения, так как WebView не привязан к релизному циклу (ещё один привет плюсу 2) и поломка может произойти в любой момент. Значит, если функционал фичи на WebView важен (особенно если экран WebView не тупиковый), требуется прогонять её интеграционные тесты как можно чаще.

При этом дополнительные трудности возникают с их стабильностью, ведь любая проблема с интернет-соединением может привести к падению теста. В случае нативных фичей мы сводим end-to-end тесты к минимуму, заменяя реальные ответы на запросы заглушками. С WebView же этот номер так просто не пройдёт, ведь он не способен к автономной работе без сети. Соответственно, если WebView не является тупиковым во флоу пользователя, то для такой фичи требуется решение, как грамотно пройти этот шаг флоу (или сымитировать это) в прогоне UI-теста соседних экранов, чтобы одновременно и повысить стабильность теста, и не сделать его бессмысленным. Требуется чётко проработать, что и как должно тестироваться на стороне Web, что на стороне натива, как протестировать интеграцию.

Рекомендации по использованию

Если охарактеризовать одним предложением рассмотренные выше тезисы, то можно с уверенностью сказать, что инструмент WebView довольно ограничен и НЕ может дать качества нативной разработки.

Мы детально рассмотрели плюсы, минусы и ограничения WebView, и теперь, на основе технических ограничений, нюансов работы и опыта использования WebView в Циан, можно перейти рекомендациям, которые мы составили для своих продуктовых команд. Вот мы и подошли к нашему ответу на вопрос: «В каких случаях использование WebView уместно, а когда лучше разрабатывать нативно?».

Когда использование WebView может быть уместно

1. MVP и эксперимент

У вас MVP фича или эксперимент, в рамках которого хочется проверить гипотезу, не затратив при этом много ресурсов.

Здесь всё понятно, в данном случае большая часть вытекающих от использования WebView недоработок не являются критичными. На старте проекта следует лишь внимательно посмотреть, позволяют ли ограничения инструмента реализовать проект даже на уровне MVP.

При завершении эксперимента на WebView следует осознанно принять решение о дальнейшем способе реализации, желательно, в сторону натива.

2. Техническая невозможность реализовать иначе

В некоторых фичах требуется использовать сторонние технологии или сервисы, реализованные только для браузера и не имеющие нативного SDK. В этом случае стоит провести ресёрч, есть ли подходящие аналоги. Если аналогов нет, то и выбора нет — придётся делать на WebView.

В этом случае нужно уделить особое внимание флоу при проработке фичи, чтобы максимально аккуратно интегрировать такую фичу в приложение.

3. Фича максимально обособлена

Что вкладывается в слово «обособлена»?

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

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

Когда у фичи простое назначение, от пользователя не требуется совершить в рамках флоу много действий, то пользователи будут испытывать меньше неудобств от WebView.

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

Это может быть какое-либо соглашение или промостраница.

Когда НЕ стоит использовать WebView

1. Стабильная фича

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

2. Техническая невозможность сделать на WebView

Здесь всё просто, мы рассмотрели ограничения и минусы, если фиче требуется возможности, которых нет у WebView, то и WebView для неё не подходит.

Обратите внимание на пункты 2, 9, 10 в списке минусов.

3. Целевой функционал

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

Минусы мы постарались подробно рассмотреть выше.

4. Фича не обособлена

Когда фича тесно не в одностороннем порядке связана с другим функционалом приложения, к WebView лучше не прибегать.

Вот небольшой чек-лист для фичи, которая может называться обособленной:

  • отдельный экран, а не блок внутри другого;
  • экран фичи является последним/тупиковым во флоу пользователя (при этом не целевой);
  • обмен данными с основным приложением сведён к минимуму.

Обратите внимание на пункты 6, 7, 11, 12 в списке минусов.

Необособленную фичу у нас в Циан обязательно нужно согласовать с командами, с функционалом которых они тесно связаны. Хотя связанные фичи у нас принято согласовывать независимо от её технологий 🙂

5. UI/UX с большим количеством интерактива

Чем больше действий требуется произвести пользователям в рамках функционала фичи, тем меньше подходит для этого WebView.

Чем больше интерактива на экране, тем сложнее для него будет загримировать WebView под натив. С чем предстоит столкнуться, мы постарались подробно рассмотреть в минусах выше.

Скажем, заполнять форму своими данными удобнее в интерфейсе с максимально привычными контролами (форма здесь просто самый очевидный пример, но не единственный).

Пример

Итак, давайте рассмотрим, как работают наши рекомендации на популярном примере экрана «Лицензионные соглашения».

Почему его реализация на WebView оправдана:

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

Для данного экрана можно даже не пытаться загримировать WebView под натив. Мы так и поступили 🙂

Итоги

Мы понимаем, что каждый проект по-своему уникален, и все возможные условия не охватить в рамках одной статьи. Мы просто постарались рассказать о возможных проблемах и собрать рекомендации, которые, надеемся, помогут критически взглянуть на вопрос использования WebView с разных сторон и принять взвешенное решение. Время, которое будет потрачено на синхронизацию и проработку этого вопроса на старте проекта, несравнимо меньше времени, которое предстоит потратить на переделки в случае ошибки.

У нас в Циан эти рекомендации не являются строгим запретом, однако все команды стараются их придерживаться. Если команда понимает, что WebView – их вариант, то:

  • Она старается проработать флоу таким образом, чтобы фича была обособлена настолько, насколько это возможно;
  • Синхронизируется о решении с командами, функционал которых потенциально может быть затронут. Это у нас и так принято делать независимо от технологий.

Так что живем дружно 🙂

  • ios
  • android
  • webview
  • разработка мобильных приложений

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *