Загрузка контента в приложении

Вы можете предоставить веб-контент, например HTML, JavaScript и CSS, для использования в приложении, которое вы статически компилируете в приложение, а не получаете через интернет.

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

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

WebViewAssetLoader

WebViewAssetLoader – это гибкий и эффективный способ загрузки контента в приложении в объект WebView. Этот класс поддерживает следующие возможности:

  • Загрузка контента с URL-адресом HTTP(S) для совместимости с правилом ограничения домена.
  • Загрузка дополнительных ресурсов, таких как JavaScript, CSS, изображения и элементы iframe.

Добавьте WebViewAssetLoader в основной файл activity. Ниже приведен пример загрузки простого веб-контента из папки assets:

Kotlin

private class LocalContentWebViewClient(private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() {
    @RequiresApi(21)
    override fun shouldInterceptRequest(
        view: WebView,
        request: WebResourceRequest
    ): WebResourceResponse? {
        return assetLoader.shouldInterceptRequest(request.url)
    }

    // To support API < 21.
    override fun shouldInterceptRequest(
        view: WebView,
        url: String
    ): WebResourceResponse? {
        return assetLoader.shouldInterceptRequest(Uri.parse(url))
    }
}

Java

private static class LocalContentWebViewClient extends WebViewClientCompat {

    private final WebViewAssetLoader mAssetLoader;

    LocalContentWebViewClient(WebViewAssetLoader assetLoader) {
        mAssetLoader = assetLoader;
    }

    @Override
    @RequiresApi(21)
    public WebResourceResponse shouldInterceptRequest(WebView view,
                                     WebResourceRequest request) {
        return mAssetLoader.shouldInterceptRequest(request.getUrl());
    }

    @Override
    @SuppressWarnings("deprecation") // To support API < 21.
    public WebResourceResponse shouldInterceptRequest(WebView view,
                                     String url) {
        return mAssetLoader.shouldInterceptRequest(Uri.parse(url));
    }
}

Вам нужно настроить экземпляр WebViewAssetLoader в соответствии с требованиями приложения. Пример приведен в следующем разделе.

Создание объектов и ресурсов в приложении

WebViewAssetLoader использует экземпляры PathHandler для загрузки ресурсов, соответствующих заданному пути. Вы можете реализовать этот интерфейс, чтобы получать ресурсы, необходимые вашему приложению, но в библиотеке Webkit есть классы AssetsPathHandler и ResourcesPathHandler для загрузки объектов и ресурсов Android соответственно.

Для начала создайте объекты и ресурсы для приложения. Как правило, действуют следующие правила:

  • Текстовые файлы, например HTML, JavaScript и CSS, относятся к объектам.
  • Изображения и другие двоичные файлы должны находиться в ресурсах.

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

  1. В Android Studio нажмите правой кнопкой мыши на папку app > src > main и выберите New > Directory (Создать > Каталог).
    Изображение, на котором показаны меню создания каталога в Android Studio
    Рисунок 1. Создайте папку assets для проекта.
  2. Назовите ее assets.
    Изображение, на котором показана папка с объектами
    Рисунок 2. Укажите название папки.
  3. Нажмите правой кнопкой мыши на папку assets и выберите New (Создать) > File (Файл). Введите index.html и нажмите клавишу Return или Ввод.
  4. Повторите предыдущий шаг, чтобы создать пустой файл для stylesheet.css.
  5. Заполните созданные пустые файлы содержимым из следующих двух примеров кода.
```html
<!-- index.html content -->

<html>
  <head>
    <!-- Tip: Use relative URLs when referring to other in-app content to give
              your app code the flexibility to change the scheme or domain as
              necessary. -->
    <link rel="stylesheet" href="/assets/stylesheet.css">
  </head>
  <body>
    <p>This file is loaded from in-app content.</p>
    <p><img src="/res/drawable/android_robot.png" alt="Android robot" width="100"></p>
  </body>
</html>
```

```css
<!-- stylesheet.css content -->

body {
  background-color: lightblue;
}
```

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

  1. Скачайте файл Android_symbol_green_RGB.png на локальный компьютер.

  2. Переименуйте файл в android_robot.png.

  3. Вручную переместите файл в каталог main/res/drawable проекта на жестком диске.

На рисунке 4 показано изображение, которое вы добавили, и текст из предыдущих примеров кода, отображаемых в приложении.

Изображение, показывающее результат рендеринга приложения
Рисунок 4. HTML-файл и графический файл, которые показываются в приложении.

Чтобы завершить создание приложения, выполните следующие действия:

  1. Зарегистрируйте обработчики и настройте AssetLoader, добавив в метод onCreate() следующий код:

    Kotlin

    val assetLoader = WebViewAssetLoader.Builder()
                           .addPathHandler("/assets/", AssetsPathHandler(this))
                           .addPathHandler("/res/", ResourcesPathHandler(this))
                           .build()
    webView.webViewClient = LocalContentWebViewClient(assetLoader)

    Java

    final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
             .addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this))
             .addPathHandler("/res/", new WebViewAssetLoader.ResourcesPathHandler(this))
             .build();
    mWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader));
  2. Загрузите контент, добавив следующий код в метод onCreate():

    Kotlin

    webView.loadUrl("https://appassets.androidplatform.net/assets/index.html")

    Java

    mWebView.loadUrl("https://appassets.androidplatform.net/assets/index.html");

Объединяйте контент в приложении с ресурсами на сайте

Приложению может потребоваться загрузить как контент в приложении, так и контент из интернета, например HTML-страницу в приложении, оформленную с помощью CSS вашего сайта. WebViewAssetLoader поддерживает этот вариант использования. Если ни один из зарегистрированных экземпляров PathHandler не может найти ресурс по указанному пути, WebView загружает контент из интернета. Если вы смешиваете контент в приложении с ресурсами сайта, зарезервируйте пути к каталогам, например /assets/ или /resources/, для ресурсов в приложении. Не храните в них ресурсы своего сайта.

Kotlin

val assetLoader = WebViewAssetLoader.Builder()
                        .setDomain("example.com") // Replace this with your website's domain.
                        .addPathHandler("/assets/", AssetsPathHandler(this))
                        .build()

webView.webViewClient = LocalContentWebViewClient(assetLoader)
val inAppHtmlUrl = "https://example.com/assets/index.html"
webView.loadUrl(inAppHtmlUrl)
val websiteUrl = "https://example.com/website/data.json"

// JavaScript code to fetch() content from the same origin.
val jsCode = "fetch('$websiteUrl')" +
        ".then(resp => resp.json())" +
        ".then(data => console.log(data));"

webView.evaluateJavascript(jsCode, null)

Java

final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
           .setDomain("example.com") // Replace this with your website's domain.
           .addPathHandler("/assets/", new AssetsPathHandler(this))
           .build();

mWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader));
String inAppHtmlUrl = "https://example.com/assets/index.html";
mWebView.loadUrl(inAppHtmlUrl);
String websiteUrl = "https://example.com/website/data.json";

// JavaScript code to fetch() content from the same origin.
String jsCode = "fetch('" + websiteUrl + "')" +
      ".then(resp => resp.json())" +
      ".then(data => console.log(data));";

mWebView.evaluateJavascript(jsCode, null);

Пример HTML-страницы в приложении, которая получает данные JSON, размещенные в интернете, можно найти в демоверсии WebView на GitHub.

loadDataWithBaseURL

Если вашему приложению нужно только загрузить HTML-страницу и не нужно перехватывать подресурсы, используйте loadDataWithBaseURL(). В этом случае объекты приложения не требуются. Пример использования приведен ниже.

Kotlin

val html = "<html><body><p>Hello world</p></body></html>"
val baseUrl = "https://example.com/"

webView.loadDataWithBaseURL(baseUrl, html, "text/html", null, baseUrl)

Java

String html = "<html><body><p>Hello world</p></body></html>";
String baseUrl = "https://example.com/";

mWebView.loadDataWithBaseURL(baseUrl, html, "text/html", null, baseUrl);

Тщательно выбирайте значения аргументов. Вот несколько советов:

  • baseUrl – URL, по которому загружается HTML-контент. Это должен быть URL HTTP(S).
  • data – HTML-контент, который вы хотите показать, в виде строки.
  • mimeType: обычно должно быть задано значение text/html.
  • encoding: не используется, если baseUrl – это URL HTTP(S), поэтому можно задать значение null.
  • historyUrl: задается то же значение, что и для baseUrl.

Мы настоятельно рекомендуем использовать в качестве baseUrl URL-адрес HTTP(S), так как это помогает обеспечить соответствие вашего приложения правилу ограничения домена.

Если вы не можете найти подходящий тип baseUrl для своего контента и предпочитаете использовать loadData(), то вам необходимо закодировать контент с помощью процентного кодирования или кодирования Base64. Мы настоятельно рекомендуем выбрать кодировку Base64 и использовать Android API, чтобы закодировать данные программно, как показано в примере ниже.

Kotlin

val encodedHtml: String = Base64.encodeToString(html.toByteArray(), Base64.NO_PADDING)

webView.loadData(encodedHtml, mimeType, "base64")

Java

String encodedHtml = Base64.encodeToString(html.getBytes(), Base64.NO_PADDING);

mWebView.loadData(encodedHtml, mimeType, "base64");

Чего следует избегать

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

  • URL-адреса file:// и data: считаются непрозрачными источниками, то есть они не могут использовать мощные веб-API, такие как fetch() или XMLHttpRequest. loadData() использует URL data:, поэтому мы рекомендуем использовать WebViewAssetLoader или loadDataWithBaseURL().
  • Хотя WebSettings.setAllowFileAccessFromFileURLs() и WebSettings.setAllowUniversalAccessFromFileURLs() могут устранить проблемы с URL-адресами file://, мы не рекомендуем устанавливать для них значение true, поскольку это делает ваше приложение уязвимым для эксплойтов на основе файлов. Для обеспечения максимальной безопасности рекомендуем явно задать для них значение false на всех уровнях API.
  • По тем же причинам мы не рекомендуем использовать URL-адреса file://android_assets/ и file://android_res/. Классы AssetsHandler и ResourcesHandler предназначены для замены.
  • Не используйте MIXED_CONTENT_ALWAYS_ALLOW. Как правило, в этом нет необходимости, и это снижает безопасность приложения. Рекомендуем загружать контент в приложении по тому же протоколу (HTTP или HTTPS), что и ресурсы сайта, и использовать MIXED_CONTENT_COMPATIBILITY_MODE или MIXED_CONTENT_NEVER_ALLOW.