Создание веб-приложений в WebView

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

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

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

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

Работа с WebView на более ранних версиях Android

Для безопасного использования более новых возможностей WebView на устройстве, на котором работает ваше приложение, добавьте библиотеку Jetpack Webkit . Это статическая библиотека, которую вы можете добавить в свое приложение для использования API android.webkit , недоступных для более ранних версий платформы.

Добавьте это в файл build.gradle следующим образом:

Котлин

dependencies {
    implementation("androidx.webkit:webkit:1.8.0")
}

Классный

dependencies {
    implementation ("androidx.webkit:webkit:1.8.0")
}

Для получения более подробной информации изучите пример WebView на GitHub.

Добавьте WebView в свое приложение.

Чтобы добавить WebView в ваше приложение, вы можете включить элемент <WebView> в макет вашей активности или установить все окно Activity в качестве WebView в onCreate() .

Добавьте WebView в макет активности.

Чтобы добавить WebView в макет вашего приложения, добавьте следующий код в XML-файл макета вашей активности:

<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

Для загрузки веб-страницы в WebView используйте loadUrl() , как показано в следующем примере:

Котлин

val myWebView: WebView = findViewById(R.id.webview)
myWebView.loadUrl("http://www.example.com")

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("http://www.example.com");

Добавьте WebView в метод onCreate().

Чтобы добавить WebView в ваше приложение в методе onCreate() активности, используйте логику, аналогичную следующей:

Котлин

val myWebView = WebView(activityContext)
setContentView(myWebView)

Java

WebView myWebView = new WebView(activityContext);
setContentView(myWebView);

Затем загрузите страницу:

Котлин

myWebView.loadUrl("http://www.example.com")

Java

myWebView.loadUrl("https://www.example.com");

Или загрузите URL-адрес из HTML-строки:

Котлин

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
val unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
val encodedHtml = Base64.encodeToString(unencodedHtml.toByteArray(), Base64.NO_PADDING)
myWebView.loadData(encodedHtml, "text/html", "base64")

Java

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
String unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(),
        Base64.NO_PADDING);
myWebView.loadData(encodedHtml, "text/html", "base64");

Вашему приложению необходим доступ к интернету. Для получения доступа к интернету запросите разрешение INTERNET в файле манифеста, как показано в следующем примере:

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

Вы можете настроить WebView , выполнив одно из следующих действий:

  • Включение полноэкранного режима с помощью WebChromeClient . Этот класс также вызывается, когда WebView требуется разрешение на изменение пользовательского интерфейса хост-приложения, например, на создание или закрытие окон или отправку диалоговых окон JavaScript пользователю. Для получения дополнительной информации об отладке в этом контексте см. раздел «Отладка веб-приложений» .
  • Обработка событий, влияющих на отображение контента, таких как ошибки при отправке форм или навигации, с помощью WebViewClient . Вы также можете использовать этот подкласс для перехвата загрузки URL-адресов.
  • Включение JavaScript путем изменения WebSettings .
  • Использование JavaScript для доступа к объектам фреймворка Android, внедренным в WebView .

Использование JavaScript в WebView

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

Включить JavaScript

В WebView по умолчанию JavaScript отключен. Вы можете включить его через WebSettings прикрепленные к вашему WebView . Получите WebSettings с помощью getSettings() , а затем включите JavaScript с помощью setJavaScriptEnabled() .

См. следующий пример:

Котлин

val myWebView: WebView = findViewById(R.id.webview)
myWebView.settings.javaScriptEnabled = true

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

WebSettings предоставляет доступ к множеству других настроек, которые могут оказаться полезными. Например, если вы разрабатываете веб-приложение, специально предназначенное для WebView в вашем Android-приложении, вы можете определить пользовательскую строку User Agent с помощью setUserAgentString() , а затем запросить этот пользовательский агент на вашей веб-странице, чтобы убедиться, что клиент, запрашивающий вашу веб-страницу, является вашим Android-приложением.

Привязка кода JavaScript к коду Android

При разработке веб-приложения, специально предназначенного для использования WebView в вашем Android-приложении, вы можете создавать интерфейсы между вашим JavaScript-кодом и клиентским Android-кодом. Например, ваш JavaScript-код может вызывать метод в вашем Android-коде для отображения Dialog , вместо использования функции alert() в JavaScript.

Чтобы связать новый интерфейс между вашим JavaScript-кодом и кодом Android, вызовите addJavascriptInterface() , передав ему экземпляр класса для привязки к вашему JavaScript-коду и имя интерфейса, к которому ваш JavaScript сможет обращаться для доступа к классу.

Чтобы узнать больше о взаимодействии между JavaScript и нативным кодом, включая более современные и безопасные API, см. раздел «Доступ к нативным API с помощью JSBridge» .

Например, вы можете включить следующий класс в свое Android-приложение:

Котлин

/** Instantiate the interface and set the context.  */
class WebAppInterface(private val mContext: Context) {

    /** Show a toast from the web page.  */
    @JavascriptInterface
    fun showToast(toast: String) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
    }
}

Java

public class WebAppInterface {
    Context mContext;

    /** Instantiate the interface and set the context. */
    WebAppInterface(Context c) {
        mContext = c;
    }

    /** Show a toast from the web page. */
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }
}

В этом примере класс WebAppInterface позволяет веб-странице создавать Toast ) с помощью метода showToast() .

Вы можете связать этот класс с JavaScript-кодом, который выполняется в вашем WebView с помощью addJavascriptInterface() , как показано в следующем примере:

Котлин

val webView: WebView = findViewById(R.id.webview)
webView.addJavascriptInterface(WebAppInterface(this), "Android")

Java

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");

Это создаёт интерфейс под названием Android для JavaScript, работающий в WebView . На этом этапе ваше веб-приложение получает доступ к классу WebAppInterface . Например, вот фрагмент HTML и JavaScript, который создаёт всплывающее сообщение с использованием нового интерфейса, когда пользователь нажимает кнопку:

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />

<script type="text/javascript">
    function showAndroidToast(toast) {
        Android.showToast(toast);
    }
</script>

Нет необходимости инициализировать интерфейс Android из JavaScript. WebView автоматически предоставляет его вашей веб-странице. Таким образом, когда пользователь нажимает кнопку, функция showAndroidToast() использует интерфейс Android для вызова метода WebAppInterface.showToast() .

Обработка навигации по страницам

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

Чтобы открыть ссылки, на которые нажимает пользователь, предоставьте WebViewClient для вашего WebView , используя setWebViewClient() . Все ссылки, на которые нажимает пользователь, загружаются в ваш WebView . Если вы хотите больше контролировать, куда загружается нажатая ссылка, создайте свой собственный WebViewClient , который переопределяет метод shouldOverrideUrlLoading() . В следующем примере предполагается, что MyWebViewClient является внутренним классом Activity .

Котлин

private class MyWebViewClient : WebViewClient() {

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        if (Uri.parse(url).host == "www.example.com") {
            // This is your website, so don't override. Let your WebView load
            // the page.
            return false
        }
        // Otherwise, the link isn't for a page on your site, so launch another
        // Activity that handles URLs.
        Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
            startActivity(this)
        }
        return true
    }
}

Java

private class MyWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        if ("www.example.com".equals(request.getUrl().getHost())) {
      // This is your website, so don't override. Let your WebView load the
      // page.
      return false;
    }
    // Otherwise, the link isn't for a page on your site, so launch another
    // Activity that handles URLs.
    Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
    startActivity(intent);
    return true;
  }
}

Затем создайте экземпляр этого нового WebViewClient для WebView :

Котлин

val myWebView: WebView = findViewById(R.id.webview)
myWebView.webViewClient = MyWebViewClient()

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());

Теперь, когда пользователь нажимает на ссылку, система вызывает метод shouldOverrideUrlLoading() , который проверяет, соответствует ли хост URL определенному домену, как определено в предыдущем примере. Если он соответствует, метод возвращает false и не переопределяет загрузку URL. Он позволяет WebView загрузить URL как обычно. Если хост URL не соответствует, создается Intent для запуска Activity по умолчанию для обработки URL, которая обращается к веб-браузеру пользователя по умолчанию.

Обработка пользовательских URL-адресов

WebView применяет ограничения при запросе ресурсов и разрешении ссылок, использующих пользовательскую схему URL. Например, если вы реализуете обратные вызовы, такие как shouldOverrideUrlLoading() или shouldInterceptRequest() , то WebView будет вызывать их только для допустимых URL-адресов.

Например, WebView может не вызывать ваш метод shouldOverrideUrlLoading() для ссылок следующего вида:

<a href="showProfile">Show Profile</a>

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

Вместо использования простой строки в ссылке, как в предыдущем примере, вы можете использовать пользовательскую схему, например, следующую:

<a href="example-app:showProfile">Show Profile</a>

Затем вы можете обработать этот URL-адрес в методе shouldOverrideUrlLoading() следующим образом:

Котлин

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
const val APP_SCHEME = "example-app:"

override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
    return if (url?.startsWith(APP_SCHEME) == true) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length), "UTF-8")
        respondToData(urlData)
        true
    } else {
        false
    }
}

Java

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
private static final String APP_SCHEME = "example-app:";

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith(APP_SCHEME)) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
        respondToData(urlData);
        return true;
    }
    return false;
}

API ` shouldOverrideUrlLoading() в первую очередь предназначен для запуска интентов для определенных URL-адресов. При его реализации убедитесь, что для обрабатываемых WebView URL-адресов возвращается false . Однако вы не ограничены запуском интентов. Вы можете заменить запуск интентов любым пользовательским поведением, описанным в приведенных выше примерах кода.

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

Например, ниже показано, как ваше Activity может использовать кнопку «Назад» на устройстве для возврата назад:

Котлин

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    // Check whether the key event is the Back button and if there's history.
    if (keyCode == KeyEvent.KEYCODE_BACK && myWebView.canGoBack()) {
        myWebView.goBack()
        return true
    }
    // If it isn't the Back button or there isn't web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // Check whether the key event is the Back button and if there's history.
    if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
        myWebView.goBack();
        return true;
    }
    // If it isn't the Back button or there's no web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event);
}

Если ваше приложение использует Jetpack AppCompat 1.6.0+, вы можете еще больше упростить предыдущий фрагмент кода:

Котлин

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack()
    }
}

Java

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack();
    }
}

Метод canGoBack() возвращает true, если у пользователя есть история посещений веб-страниц. Аналогично, вы можете использовать canGoForward() для проверки наличия истории перемотки вперед. Если вы не выполняете эту проверку, то после того, как пользователь достигнет конца истории, goBack() и goForward() ничего не делают.

Чтобы сохранить историю навигации при повторном создании активности или освобождении ресурсов фонового процесса, используйте saveState() для сериализации истории навигации WebView в Bundle и restoreState() для ее восстановления. Рекомендации по безопасному управлению ограничениями размера сохраненного состояния и предотвращению TransactionTooLargeException см. в разделе «Эффективное управление состоянием WebView» .

Обработка изменений конфигурации устройства

Во время выполнения программы изменения состояния активности происходят при изменении конфигурации устройства, например, когда пользователь поворачивает устройство или закрывает редактор методов ввода (IME). Эти изменения приводят к уничтожению активности объекта WebView и созданию новой активности, которая также создает новый объект WebView , загружающий URL-адрес уничтоженного объекта. Вы можете использовать saveState() внутри onSaveInstanceState() и restoreState() при повторном создании объекта, чтобы восстановить контекст навигации пользователя. Для безопасного управления размером состояния см. раздел «Эффективное управление состоянием WebView» .

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

Управление окнами

По умолчанию запросы на открытие новых окон игнорируются. Это справедливо как для окон, открываемых с помощью JavaScript, так и для окон, открываемых атрибутом target в ссылке. Вы можете настроить WebChromeClient , чтобы задать собственное поведение при открытии нескольких окон одновременно.

Для повышения безопасности вашего приложения лучше всего предотвратить открытие всплывающих окон и новых окон. Самый безопасный способ реализовать это поведение — передать значение "true" в метод setSupportMultipleWindows() , но не переопределять метод onCreateWindow() , от которого зависит setSupportMultipleWindows() . Эта логика предотвращает загрузку любой страницы, в ссылках которой используется target="_blank" .