Carga contenido integrado en la app

Puedes proporcionar contenido basado en la Web, como HTML, JavaScript y CSS, para que tu app use tu compilación estática en la app en lugar de recuperarla a través de Internet.

El contenido integrado en la app no requiere acceso a Internet ni consume el ancho de banda del usuario. Si el contenido está diseñado solo para WebView (es decir, depende de la comunicación con una app nativa), los usuarios no podrán cargarlo accidentalmente en un navegador web.

Sin embargo, el contenido integrado en la app tiene algunas desventajas. La actualización del contenido basado en la Web requiere el envío de una nueva actualización de la app, y, si los usuarios tienen versiones desactualizadas de la app, existe la posibilidad de que haya contenido incoherente entre el contenido de un sitio web y el de la app del dispositivo.

WebViewAssetLoader

WebViewAssetLoader es una forma flexible y eficaz de cargar contenido integrado en la app en un objeto WebView. Esta clase admite lo siguiente:

  • Carga contenido con una URL HTTP(S) para brindar compatibilidad con la política del mismo origen
  • Carga de subrecursos, como JavaScript, CSS, iframes y imágenes

Incluye WebViewAssetLoader en tu archivo de actividad principal. El siguiente es un ejemplo de carga de contenido web simple desde la carpeta de elementos:

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));
    }
}

Tu app debe configurar una instancia de WebViewAssetLoader para satisfacer sus necesidades. La siguiente sección tiene un ejemplo.

Cómo crear recursos y recursos integrados en la app

WebViewAssetLoader se basa en instancias de PathHandler para cargar los recursos correspondientes a una ruta de recurso determinada. Si bien puedes implementar esta interfaz para recuperar recursos según sea necesario por tu app, la biblioteca de Webkit empaqueta AssetsPathHandler y ResourcesPathHandler para cargar recursos y recursos de Android, respectivamente.

Para comenzar, crea elementos y recursos para tu app. En general, se aplica lo siguiente:

  • Los archivos de texto, como HTML, JavaScript y CSS, pertenecen a los recursos.
  • Las imágenes y otros archivos binarios pertenecen a los recursos.

Para agregar archivos web basados en texto a un proyecto, haz lo siguiente:

  1. En Android Studio, haz clic con el botón derecho en la carpeta app > src > main y selecciona New > Directory.
    Imagen que muestra los menús de creación del directorio de Android Studio
    Figura 1: Crea una carpeta de elementos para tu proyecto.
  2. Asígnele el nombre "assets" a la carpeta.
    Una imagen que muestra la carpeta de recursos
    Figura 2: Asigna un nombre a la carpeta de recursos.
  3. Haz clic con el botón derecho en la carpeta assets y selecciona New > File. Ingresa index.html y presiona la tecla Retorno o Intro.
  4. Repite el paso anterior a fin de crear un archivo vacío para stylesheet.css.
  5. Completa los archivos vacíos que creaste con el contenido de las siguientes dos muestras de código.
```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;
}
```

Para agregar un archivo web basado en imágenes a tu proyecto, haz lo siguiente:

  1. Descarga el archivo Android_symbol_green_RGB.png a tu máquina local.

  2. Cambia el nombre del archivo a android_robot.png.

  3. Mueve el archivo de forma manual al directorio main/res/drawable del proyecto en el disco duro.

En la Figura 4, se muestra la imagen que agregaste y el texto de las muestras de código anteriores renderizadas en una app.

Una imagen que muestra el resultado renderizado de la app
Figura 4: Archivo HTML de la app y archivo de imagen renderizado en una app

Para completar la app, haz lo siguiente:

  1. Para registrar los controladores y configurar AssetLoader, agrega el siguiente código al método 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. Para cargar el contenido, agrega el siguiente código al método onCreate():

    Kotlin

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

    Java

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

Combina el contenido de la app con recursos de tu sitio web

Es posible que tu app necesite cargar una combinación de contenido integrado en ella y contenido de Internet, como una página HTML en la app con el estilo del CSS de tu sitio web. WebViewAssetLoader admite este caso de uso. Si ninguna de las instancias de PathHandler registradas puede encontrar un recurso para la ruta determinada, WebView recurre a la carga de contenido de Internet. Si combinas contenido en la app con recursos de tu sitio web, reserva rutas de acceso a directorios, como /assets/ o /resources/, para los recursos de la app. Evita almacenar recursos de tu sitio web en esas ubicaciones.

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);

Consulta la demostración de WebView en GitHub para ver un ejemplo de una página HTML en la app que recupera datos JSON alojados en la Web.

loadDataWithBaseURL

Si tu app solo necesita cargar una página HTML y no necesita interceptar subrecursos, considera usar loadDataWithBaseURL(), que no requiere recursos de app. Puedes usarlo como se indica en la siguiente muestra de código:

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);

Elige los valores de los argumentos con cuidado. Ten en cuenta lo siguiente:

  • baseUrl: Esta es la URL como se carga tu contenido HTML. Debe ser una URL HTTP(S).
  • data: Este es el contenido HTML que deseas mostrar, como una cadena.
  • mimeType: Por lo general, se debe establecer en text/html.
  • encoding: No se usa cuando baseUrl es una URL HTTP(S), por lo que se puede configurar como null.
  • historyUrl: Se establece con el mismo valor que baseUrl.

Te recomendamos que uses una URL HTTP(S) como baseUrl, ya que esto ayuda a garantizar que tu app cumpla con la política del mismo origen.

Si no encuentras un baseUrl adecuado para tu contenido y prefieres usar loadData(), debes codificar el contenido con codificación por ciento o codificación Base64. Te recomendamos que elijas la codificación Base64 y uses las APIs de Android para codificarla de manera programática, como se muestra en la siguiente muestra de código:

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");

Elementos que debes evitar

Existen varias otras formas de cargar contenido integrado en la app, pero te recomendamos que las uses:

  • Se considera que las URLs file:// y las URLs data: son orígenes opacos, lo que significa que no pueden aprovechar APIs web potentes como fetch() o XMLHttpRequest. loadData() usa URLs data: de forma interna, por lo que te recomendamos que uses WebViewAssetLoader o loadDataWithBaseURL() en su lugar.
  • Si bien WebSettings.setAllowFileAccessFromFileURLs() y WebSettings.setAllowUniversalAccessFromFileURLs() pueden solucionar los problemas con las URLs de file://, te recomendamos que no las configures como true, ya que esto dejará a tu app vulnerable a vulnerabilidades basadas en archivos. Te recomendamos configurarlas de forma explícita como false en todos los niveles de API para obtener la mayor seguridad.
  • Por los mismos motivos, recomendamos no usar las URLs file://android_assets/ y file://android_res/. Las clases AssetsHandler y ResourcesHandler están pensadas para ser reemplazos directos.
  • Evita usar MIXED_CONTENT_ALWAYS_ALLOW. Por lo general, esta configuración no es necesaria y debilita la seguridad de tu app. Te recomendamos que cargues el contenido integrado en la app en el mismo esquema (HTTP o HTTPS) como los recursos de tu sitio web y que uses MIXED_CONTENT_COMPATIBILITY_MODE o MIXED_CONTENT_NEVER_ALLOW, según corresponda.