Cómo administrar objetos WebView

Android ofrece varias API para ayudarte a administrar los objetos WebView que muestran contenido web en tu app.

En esta página, se describe la forma de usar esas API para trabajar con objetos WebView de manera más eficaz y así mejorar la estabilidad y seguridad de tu app.

Versión de API

A partir de Android 7.0 (nivel de API 24), los usuarios pueden elegir diferentes paquetes para mostrar contenido web en un objeto WebView. Android 8.0 (nivel de API 26) y las versiones posteriores incluyen una API para obtener información relacionada con el paquete que muestra contenido web en tu app. Esta API es particularmente útil para analizar errores que ocurren solo cuando tu app intenta mostrar contenido web usando una implementación de WebView que realiza un paquete específico.

Para usar esta API, agrega la lógica que se muestra en el siguiente fragmento de código:

Kotlin

val webViewPackageInfo = WebView.getCurrentWebViewPackage()
Log.d("MY_APP_TAG", "WebView version: ${webViewPackageInfo.versionName}")

Java

PackageInfo webViewPackageInfo = WebView.getCurrentWebViewPackage();
Log.d("MY_APP_TAG", "WebView version: " + webViewPackageInfo.versionName);

Nota: El método WebView.getCurrentWebViewPackage() puede mostrar null si el dispositivo no se configuró correctamente. También muestra null si ejecutas tu app en un dispositivo que no es compatible con WebView, como un dispositivo Wear OS.

API para Navegación Segura de Google

A fin de brindar a tus usuarios una experiencia de navegación más segura, puedes configurar los objetos WebView de tu app para verificar URL que usan la Navegación Segura de Google, que le permite a tu app mostrarles a los usuarios una advertencia cuando intenta navegar en un sitio web potencialmente peligroso.

Si bien el valor verdadero de EnableSafeBrowsing es verdadero, hay algunos casos en los que es posible que solo quieras habilitar o inhabilitar la Navegación segura de forma condicional. Android 8.0 (nivel de API 26) y versiones posteriores son compatibles con setSafeBrowsingEnabled() Las apps que se compilan en niveles de API inferiores no pueden usar setSafeBrowsingEnabled() y deberían cambiar el valor de EnableSafeBrowsing a falso en el manifiesto a fin de inhabilitar la función para todas las instancias de WebView.

Si tu app está dirigida a Android 7.1 (nivel de API 25) o versiones anteriores, puedes inhabilitar tus objetos WebView para que no verifiquen URL en la lista de Navegación Segura de Google de sitios web no seguros agregando el siguiente elemento <meta-data> al archivo de manifiesto de tu app:

<manifest>
    <application>
        <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
                   android:value="false" />
        ...
    </application>
</manifest>

Cómo definir acciones programáticas

Cuando una instancia WebView intenta cargar una página que Google clasificó como una amenaza conocida WebView muestra, de forma predeterminada, un anuncio intersticial que advierte a los usuarios sobre la amenaza conocida. Esta pantalla da a los usuarios la opción de cargar la URL de todas maneras o regresar a una página anterior que sea segura.

Si apuntas a Android 8.1 (nivel de API 27) o versiones posteriores, puedes definir mediante programación la manera en que la app responde a una amenaza conocida:

  • Puedes controlar el hecho de tu app informe amenazas conocidas a Navegación segura.
  • Puedes hacer que tu app realice automáticamente una acción específica, como regresar a un estado seguro, cada vez que encuentre una URL que se haya clasificado como amenaza conocida.

Nota: Para lograr una protección óptima contra amenazas conocidas, espera hasta inicializar la Navegación segura antes de invocar el método loadUrl() de un objeto WebView.

En el siguiente fragmento de código, se muestra la instrucción que puedes dar a las instancias de WebView de tu app para que siempre regresen a un estado seguro después de encontrar una amenaza conocida:

MyWebActivity.java

Kotlin

private lateinit var superSafeWebView: WebView
private var safeBrowsingIsInitialized: Boolean = false

// ...

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    superSafeWebView = WebView(this)
    superSafeWebView.webViewClient = MyWebViewClient()
    safeBrowsingIsInitialized = false

    superSafeWebView.startSafeBrowsing(this, ValueCallback<Boolean> { success ->
        safeBrowsingIsInitialized = true
        if (!success) {
            Log.e("MY_APP_TAG", "Unable to initialize Safe Browsing!")
        }
    })
}

Java

private WebView superSafeWebView;
private boolean safeBrowsingIsInitialized;

// ...

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    superSafeWebView = new WebView(this);
    superSafeWebView.setWebViewClient(new MyWebViewClient());
    safeBrowsingIsInitialized = false;

    superSafeWebView.startSafeBrowsing(this, new ValueCallback<Boolean>() {
        @Override
        public void onReceiveValue(Boolean success) {
            safeBrowsingIsInitialized = true;
            if (!success) {
                Log.e("MY_APP_TAG", "Unable to initialize Safe Browsing!");
            }
        }
    });
}

MyWebViewClient.java

Kotlin

class MyWebViewClient : WebViewClient() {
    // Automatically go "back to safety" when attempting to load a website that
    // Google has identified as a known threat. An instance of WebView calls
    // this method only after Safe Browsing is initialized, so there's no
    // conditional logic needed here.
    override fun onSafeBrowsingHit(
            view: WebView,
            request: WebResourceRequest,
            threatType: Int,
            callback: SafeBrowsingResponse
    ) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        callback.backToSafety(true)
        Toast.makeText(view.context, "Unsafe web page blocked.", Toast.LENGTH_LONG).show()
    }
}

Java

public class MyWebViewClient extends WebViewClient {
    // Automatically go "back to safety" when attempting to load a website that
    // Google has identified as a known threat. An instance of WebView calls
    // this method only after Safe Browsing is initialized, so there's no
    // conditional logic needed here.
    @Override
    public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
            int threatType, SafeBrowsingResponse callback) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        callback.backToSafety(true);
        Toast.makeText(view.getContext(), "Unsafe web page blocked.",
                Toast.LENGTH_LONG).show();
    }
}

API de Geolocation HTML5

Para las apps dirigidas a Android 6.0 (nivel de API 23) y versiones posteriores, la API de Geolocation solo es compatible con orígenes seguros, como HTTPS. Cualquier solicitud a la API de Geolocation sobre orígenes no seguros es automáticamente denegada sin invocar el método correspondiente onGeolocationPermissionsShowPrompt().

Cómo inhabilitar la colección de métricas

WebView permite subir datos de diagnóstico anónimos en Google cuando el usuario ha dado su consentimiento. Los datos se recopilan de forma individual para cada app que tenga una instancia de WebView. Puedes inhabilitar esta función creando la siguiente etiqueta en el elemento <application> del manifiesto:

<manifest>
    <application>
    ...
    <meta-data android:name="android.webkit.WebView.MetricsOptOut"
               android:value="true" />
    </application>
</manifest>

Los datos solo se cargarán desde una app si el usuario ha dado su consentimiento y no se ha inhabilitado la app.

API de Termination Handling

Esta API aborda casos en los que desaparece el proceso del representador para un objeto WebView, ya sea porque el sistema eliminó el representador para reclamar la memoria que tanto necesita o porque falló el proceso del representador. Al usar esta API, permites que la app continúe ejecutándose aunque el proceso del representador haya desaparecido.

Advertencia: Si tu app continúa ejecutándose después de que desaparece el proceso del representador, la instancia asociada de WebView no se podrá volver a utilizar, independientemente de que el proceso del representador se haya eliminado o haya fallado. Tu app debe eliminar la instancia de la jerarquía de vistas y destruirla para que continúe ejecutándose. Luego, tu app debe crear una instancia completamente nueva de WebView para continuar representando páginas web.

Ten en cuenta que, si un representador falla mientras carga una página web específica, el intento de cargar esa misma página nuevamente podría hacer que un nuevo objeto WebView exhiba el mismo comportamiento de falla de representación.

En el siguiente fragmento de código, se ilustra la manera de usar esta API:

Kotlin

inner class MyRendererTrackingWebViewClient : WebViewClient() {
    private var mWebView: WebView? = null

    override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
        if (!detail.didCrash()) {
            // Renderer was killed because the system ran out of memory.
            // The app can recover gracefully by creating a new WebView instance
            // in the foreground.
            Log.e("MY_APP_TAG", ("System killed the WebView rendering process " +
                "to reclaim memory. Recreating..."))

            mWebView?.also { webView ->
                val webViewContainer: ViewGroup = findViewById(R.id.my_web_view_container)
                webViewContainer.removeView(webView)
                webView.destroy()
                mWebView = null
            }

            // By this point, the instance variable "mWebView" is guaranteed
            // to be null, so it's safe to reinitialize it.

            return true // The app continues executing.
        }

        // Renderer crashed because of an internal error, such as a memory
        // access violation.
        Log.e("MY_APP_TAG", "The WebView rendering process crashed!")

        // In this example, the app itself crashes after detecting that the
        // renderer crashed. If you choose to handle the crash more gracefully
        // and allow your app to continue executing, you should 1) destroy the
        // current WebView instance, 2) specify logic for how the app can
        // continue executing, and 3) return "true" instead.
        return false
    }
}

Java

public class MyRendererTrackingWebViewClient extends WebViewClient {
    private WebView mWebView;

    @Override
    public boolean onRenderProcessGone(WebView view,
            RenderProcessGoneDetail detail) {
        if (!detail.didCrash()) {
            // Renderer was killed because the system ran out of memory.
            // The app can recover gracefully by creating a new WebView instance
            // in the foreground.
            Log.e("MY_APP_TAG", "System killed the WebView rendering process " +
                    "to reclaim memory. Recreating...");

            if (mWebView != null) {
                ViewGroup webViewContainer =
                        (ViewGroup) findViewById(R.id.my_web_view_container);
                webViewContainer.removeView(mWebView);
                mWebView.destroy();
                mWebView = null;
            }

            // By this point, the instance variable "mWebView" is guaranteed
            // to be null, so it's safe to reinitialize it.

            return true; // The app continues executing.
        }

        // Renderer crashed because of an internal error, such as a memory
        // access violation.
        Log.e("MY_APP_TAG", "The WebView rendering process crashed!");

        // In this example, the app itself crashes after detecting that the
        // renderer crashed. If you choose to handle the crash more gracefully
        // and allow your app to continue executing, you should 1) destroy the
        // current WebView instance, 2) specify logic for how the app can
        // continue executing, and 3) return "true" instead.
        return false;
    }
}

API de Renderer Importance

Ahora que los objetos WebView funcionan en el modo multiproceso, cuentas con flexibilidad respecto de la manera en que tu app maneja situaciones de falta de memoria. Puedes usar la API de Renderer Importance, introducida en Android 8.0, a fin de que establezca una política de prioridad para el representador asignado a un objeto WebView específico. En particular, podrías desear que la parte principal de tu app continúe ejecutándose cuando se elimine un representador que muestre los objetos WebView de la app. Podrías hacer esto, por ejemplo, si no quieres mostrar el objeto WebView por un tiempo prolongado, de modo que el sistema pueda recuperar la memoria usada por el representador.

En el siguiente fragmento de código, se muestra la manera de asignar una prioridad al proceso del representador asociado con los objetos WebView de tu app:

Kotlin

val myWebView: WebView = ...
myWebView.setRendererPriorityPolicy(RENDERER_PRIORITY_BOUND, true)

Java

WebView myWebView;
myWebView.setRendererPriorityPolicy(RENDERER_PRIORITY_BOUND, true);

En este fragmento particular, la prioridad del representador es la misma que (o está "vinculada a") la prioridad predeterminada para la app. El argumento true reduce la prioridad del representador a RENDERER_PRIORITY_WAIVED cuando el objeto WebView asociado deja de ser visible. En otras palabras, un argumento true indica que a tu app no le importa si el sistema mantiene activo el proceso del representador. De hecho, este nivel de prioridad más bajo hace posible que se elimine el proceso del representador en situaciones de falta de memoria.

Advertencia: Para mantener la estabilidad de la app, no debes cambiar la política de prioridad del representador para un objeto WebView a menos que también uses la Termination Handle API para especificar la manera en que WebView reacciona cuando desaparece el representador asociado.

Para obtener más información sobre la manera en que el sistema maneja situaciones de baja memoria, consulta Procesos y ciclo de vida de la aplicación.