Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Migración a WebView en Android 4.4

Android 4.4 (API nivel 19) presenta una nueva versión de WebView basada en Chromium. Este cambio actualiza el rendimiento de WebView y la compatibilidad de los estándares para HTML5, CSS3 y JavaScript a fin de que coincidan con los navegadores web más recientes. Las apps que usen WebView heredarán estas actualizaciones cuando se ejecuten en Android 4.4 y versiones posteriores.

En este documento, se describen los cambios adicionales a WebView que debes tener en cuenta si estableces tu targetSdkVersion en "19" o una versión posterior.

Nota: Si tu targetSdkVersion está configurada en "18" o versiones anteriores, WebView funciona en "modo no estándar" para evitar algunos de los cambios de comportamiento que se describen a continuación, lo más detalladamente posible, al tiempo que sigue proporcionando a tu app las actualizaciones de rendimiento y de estándares web. Sin embargo, ten en cuenta que los diseños de columna única y estrecha y los niveles de zoom predeterminados no se admiten en Android 4.4, y puede haber otras diferencias de comportamiento no identificadas. Por lo tanto, asegúrate de probar la app en Android 4.4 o versiones posteriores, incluso si mantienes tu targetSdkVersion configurada en "18" o versiones anteriores.

Para solucionar posibles problemas al migrar tu app a WebView en Android 4.4., puedes habilitar la depuración remota mediante Chrome en la computadora de escritorio llamando a setWebContentsDebuggingEnabled(). Esta nueva función en WebView te permite inspeccionar y analizar el contenido web, las secuencias de comandos y la actividad de red mientras se ejecutan en una WebView. Para obtener más información, consulta el artículo Depuración remota en Android.

Cambios del usuario-agente

Si publicas contenido en tu WebView en función del usuario-agente, debes tener en cuenta que la string del usuario-agente tuvo algunos cambios menores y ahora incluye la versión de Chrome:

    Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) AppleWebKit/537.36
    (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
    

Si debes recuperar el usuario-agente, pero no necesitas almacenarlo para tu app o no quieres crear una instancia de WebView, tienes que usar el método estático getDefaultUserAgent(). No obstante, si planeas anular la string del usuario-agente en tu WebView, quizás te convenga usar getUserAgentString().

Bloqueo de subprocesos y subprocesos múltiples

Si llamas a métodos de WebView desde cualquier subproceso que no sea el subproceso de IU de tu app, pueden generarse resultados inesperados. Por ejemplo, si la app usa subprocesos múltiples, puedes utilizar el método runOnUiThread() para asegurarte de que tu código se ejecute en el subproceso de IU:

Kotlin

    runOnUiThread {
        // Code for WebView goes here
    }
    

Java

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            // Code for WebView goes here
        }
    });
    

También asegúrate de no bloquear el subproceso de IU en ningún caso. En algunas apps se produce este error mientras esperan una devolución de llamada de JavaScript. Por ejemplo, no uses un código como este:

Kotlin

    // This code is BAD and will block the UI thread
    webView.loadUrl("javascript:fn()")
    while (result == null) {
        Thread.sleep(100)
    }
    

Java

    // This code is BAD and will block the UI thread
    webView.loadUrl("javascript:fn()");
    while(result == null) {
      Thread.sleep(100);
    }
    

En cambio, puedes usar un método nuevo, evaluateJavascript(), para ejecutar JavaScript de manera asíncrona.

Cómo manejar URL personalizadas

La nueva WebView aplica restricciones adicionales cuando solicita recursos y resuelve vínculos que utilizan un esquema de URL personalizada. Por ejemplo, si implementas devoluciones de llamada como shouldOverrideUrlLoading() o shouldInterceptRequest(), WebView las invoca solo para URL válidas.

Si usas un esquema de URL personalizada o una URL base y notas que tu app recibe menos llamadas a estas devoluciones de llamada o no carga recursos en Android 4.4, asegúrate de que las solicitudes especifiquen URL válidas que cumplan con la especificación RFC 3986.

Por ejemplo, es posible que la nueva WebView no llame a tu método shouldOverrideUrlLoading() para vínculos como el siguiente:

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

El resultado de que el usuario haga clic en dicho vínculo puede variar:

  • Si cargaste la página llamando a loadData() o loadDataWithBaseURL() con una URL base no válida o nula, no recibirás la devolución de llamada shouldOverrideUrlLoading() para este tipo de vínculo en la página.

    Nota: Cuando uses loadDataWithBaseURL() y la URL base no sea válida o esté establecida como nula, todos los vínculos del contenido que cargues deberán ser absolutos.

  • Si llamaste a loadUrl() para cargar la página o proporcionaste una URL base válida con loadDataWithBaseURL(), recibirás la devolución de llamada shouldOverrideUrlLoading() para este tipo de vínculo en la página, pero la URL que recibirás será absoluta y corresponderá a la página actual. Por ejemplo, la URL que recibas será "http://www.example.com/showProfile" en lugar de solo "showProfile".

En lugar de usar una string simple en un vínculo como se muestra más arriba, puedes utilizar un esquema personalizado como el siguiente:

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

Luego, puedes manejar esta URL en tu método shouldOverrideUrlLoading() de la siguiente manera:

Kotlin

    // The URL scheme should be non-hierarchical (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 should be non-hierarchical (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;
    }
    

Si no puedes modificar el código HTML, tal vez puedas usar loadDataWithBaseURL() y establecer una URL base que conste de un esquema personalizado y un host válido, como "example-app://<valid_host_name>/". Por ejemplo:

Kotlin

    webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA, null, "UTF-8", null)
    

Java

    webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA,
            null, "UTF-8", null);
    

El nombre de host válido debe cumplir con la especificación RFC 3986. Es importante incluir la barra final, ya que, de lo contrario, las solicitudes de la página cargada podrían perderse.

Cambios del viewport

Ya no se admite la propiedad target-densitydpi del viewport

Anteriormente, WebView admitía una propiedad del viewport llamada target-densitydpi para ayudar a las páginas web a especificar la densidad de pantalla deseada. Esta propiedad ya no es compatible y debes migrar al uso de soluciones estándar con imágenes y CSS como se describe en el artículo IU de Pixel Perfect en WebView.

El viewport hace un acercamiento cuando es pequeño

Anteriormente, si configurabas el ancho del viewport en un valor inferior o igual a "320", se asignaba a "device-width" y, si configurabas el alto en un valor inferior o igual al alto de la WebView, se asignaba a "device-height". Sin embargo, cuando se ejecuta en la nueva WebView, se respeta el valor de ancho o alto y la WebView hace un acercamiento para llenar el ancho de la pantalla.

No se admiten múltiples etiquetas de viewport

Anteriormente, si incluías varias etiquetas de viewport en una página web, WebView fusionaba las propiedades de todas las etiquetas. En la nueva WebView, solo se usa el último viewport y se ignoran todos los demás.

El zoom predeterminado dejó de estar disponible

Ya no se admiten los métodos getDefaultZoom() y setDefaultZoom() para obtener y establecer el nivel de zoom inicial en una página. En cambio, debes definir el viewport adecuado en la página web.

Precaución: Estas API no se admiten en Android 4.4 y versiones posteriores. Incluso aunque tu targetSdkVersion esté configurada en "18" o versiones anteriores, las API no tendrán efecto.

Para obtener información sobre cómo definir las propiedades del viewport en el código HTML, consulta el artículo IU de Pixel Perfect en WebView.

Si no puedes establecer el ancho del viewport en el código HTML, debes llamar a setUseWideViewPort() para asegurarte de que la página tenga un viewport más amplio. Por ejemplo:

Kotlin

    webView.settings.apply {
        useWideViewPort = true
        loadWithOverviewMode = true
    }
    

Java

    WebSettings settings = webView.getSettings();
    settings.setUseWideViewPort(true);
    settings.setLoadWithOverviewMode(true);
    

Cambios de estilo

La propiedad abreviada de CSS background anula la propiedad background-size

Chrome y otros navegadores se comportaron de esta manera durante un tiempo, pero ahora WebView anulará además una configuración de CSS para background-size si también especificas el estilo background. Por ejemplo, aquí el tamaño se restablecerá a un valor predeterminado:

    .some-class {
      background-size: contain;
      background: url('images/image.png') no-repeat;
    }
    

Para solucionarlo, solo hay que intercambiar las dos propiedades.

    .some-class {
      background: url('images/image.png') no-repeat;
      background-size: contain;
    }
    

Los tamaños están expresados en píxeles de CSS y no en píxeles de pantalla

Anteriormente, los parámetros de tamaño, como window.outerWidth y window.outerHeight, mostraban un valor en píxeles de pantalla reales. En la nueva WebView, muestran un valor basado en píxeles de CSS.

No se considera buena práctica intentar calcular el tamaño físico en píxeles para determinar el tamaño de los elementos u otros valores. Sin embargo, si inhabilitaste el zoom y la escala inicial está configurada en 1.0, puedes usar window.devicePixelRatio para obtener la escala y luego multiplicarla por el valor de píxeles de CSS. Como alternativa, puedes crear una vinculación de JavaScript para buscar el tamaño de píxeles desde la WebView.

Para obtener más información, consulta el sitio quirksmode.org.

NARROW_COLUMNS y SINGLE_COLUMN ya no son compatibles

El valor NARROW_COLUMNS para WebSettings.LayoutAlgorithm no se admite en la nueva WebView.

Precaución: Estas API no se admiten en Android 4.4 y versiones posteriores. Incluso aunque tu targetSdkVersion esté configurada en "18" o versiones anteriores, las API no tendrán efecto.

Puedes manejar el cambio de las siguientes maneras:

  • Modifica los estilos de tu aplicación:

    Si controlas el código HTML y CSS en la página, es posible que alterar el diseño del contenido sea el enfoque más confiable. Por ejemplo, para las pantallas donde citas licencias, quizás quieras unir el texto dentro de una etiqueta <pre>, algo que puedes lograr con los siguientes estilos:

    <pre style="word-wrap: break-word; white-space: pre-wrap;">

    Esta acción puede ser útil en especial si no definiste las propiedades del viewport para tu página.

  • Usa el nuevo algoritmo de diseño TEXT_AUTOSIZING:

    Si utilizabas columnas estrechas como una forma de hacer que un amplio espectro de sitios de escritorio fueran más legibles en dispositivos móviles y no puedes cambiar el contenido HTML, el nuevo algoritmo TEXT_AUTOSIZING puede ser una alternativa adecuada a NARROW_COLUMNS.

Además, el valor SINGLE_COLUMN, que ya estaba obsoleto, tampoco se admite en la nueva WebView.

Cómo manejar eventos táctiles en JavaScript

Si tu página web maneja directamente los eventos táctiles en una WebView, asegúrate de manejar también el evento touchcancel. Hay algunos casos en los que se llamará a touchcancel, lo que puede causar problemas si no se recibe:

  • Se toca un elemento (entonces se llama a touchstart y a touchmove) y la página se desplaza, lo que da lugar a touchcancel.
  • Se toca un elemento (se llama a touchstart), pero no se llama a event.preventDefault(), lo que da lugar a touchcancel con suficiente antelación (por lo que WebView presupone que no deseas consumir los eventos táctiles).