Cómo administrar objetos WebView

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

En esta página, se describe cómo usar estas APIs para trabajar con objetos WebView de manera más eficaz y mejorar la estabilidad y seguridad de tu app.

API de versión

A partir de Android 7.0 (nivel de API 24), los usuarios pueden elegir entre varios paquetes diferentes para mostrar contenido web en un objeto WebView. La biblioteca de AndroidX.webkit incluye el método getCurrentWebViewPackage() para recuperar información relacionada con el paquete que muestra contenido web en tu app. Este método es útil cuando se analizan errores que ocurren solo cuando tu app intenta mostrar contenido web con la implementación de un paquete en particular de WebView.

Para usar este método, agrega la lógica que se muestra en el siguiente fragmento de código:

Kotlin

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

Java

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

Servicio de Navegación segura de Google

Para brindarles a los usuarios una experiencia de navegación más segura, los objetos WebView verifican las URLs con la Navegación segura de Google, que permite que tu app muestre una advertencia cuando intentan navegar a un sitio web potencialmente inseguro.

Aunque el valor predeterminado de EnableSafeBrowsing es verdadero, hay casos en los que es posible que solo quieras habilitar la Navegación segura de forma condicional o inhabilitarla. Android 8.0 (nivel de API 26) y las versiones posteriores admiten el uso de setSafeBrowsingEnabled() para activar o desactivar la Navegación segura en un objeto WebView individual.

Si deseas que todos los objetos WebView inhabiliten las verificaciones de Navegación segura, agrega 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 de WebView intenta cargar una página que Google clasifica como una amenaza conocida, el WebView muestra de forma predeterminada un anuncio intersticial que advierte a los usuarios sobre la amenaza conocida. Esta pantalla les brinda a los usuarios la opción de cargar la URL de todas maneras o regresar a una página anterior que sea segura.

Si orientas tu app a Android 8.1 (nivel de API 27) o versiones posteriores, puedes definir de manera programática cómo responde tu app ante una amenaza conocida de las siguientes maneras:

  • Puedes controlar si tu app informa 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 clasificada como amenaza conocida.

En los siguientes fragmentos de código, se muestra cómo indicar a las instancias de WebView de tu app 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

    if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
        WebViewCompat.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;

    if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
        WebViewCompat.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 : WebViewClientCompat() {
    // Automatically go "back to safety" when attempting to load a website that
    // Google identifies 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: SafeBrowsingResponseCompat
    ) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)) {
            callback.backToSafety(true)
            Toast.makeText(view.context, "Unsafe web page blocked.", Toast.LENGTH_LONG).show()
        }
    }
}

Java

public class MyWebViewClient extends WebViewClientCompat {
    // Automatically go "back to safety" when attempting to load a website that
    // Google identifies 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, SafeBrowsingResponseCompat callback) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)) {
            callback.backToSafety(true);
            Toast.makeText(view.getContext(), "Unsafe web page blocked.",
                    Toast.LENGTH_LONG).show();
        }
    }
}

API de HTML5 Geolocation

En el caso de las apps orientadas 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 desde orígenes no seguros se rechaza automáticamente sin invocar el método onGeolocationPermissionsShowPrompt() correspondiente.

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

WebView tiene la capacidad de subir datos de diagnóstico anónimos a Google cuando el usuario da su consentimiento. Los datos se recopilan por app para cada app que crea 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 suben desde una app si el usuario da su consentimiento y la app no la rechaza. Si quieres obtener más información para inhabilitar los informes de datos de diagnóstico, consulta Privacidad del usuario en los informes de WebView.

API de Termination Handling

La API de Finished Handling controla los casos en los que desaparece el proceso del renderizador para un objeto WebView, ya sea porque el sistema finaliza el procesador para recuperar la memoria necesaria o porque falla el proceso del procesador. Si usas esta API, permites que tu app continúe ejecutándose aunque el proceso del procesador desaparezca.

Si un procesador falla mientras se carga una página web específica, intentar cargar esa misma página puede hacer que un nuevo objeto WebView exhiba el mismo comportamiento de falla de renderización.

En el siguiente fragmento de código, se muestra cómo usar esta API en un Activity:

Kotlin

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

    override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
        if (!detail.didCrash()) {
            // Renderer is 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 crashes 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 handle the crash more gracefully and let
        // your app continue executing, you must destroy the current WebView
        // instance, specify logic for how the app continues executing, and
        // 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 is 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 crashes 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 handle the crash more gracefully and let
        // your app continue executing, you must destroy the current WebView
        // instance, specify logic for how the app continues executing, and
        // return "true" instead.
        return false;
    }
}

API de Renderer Importance

Cuando los objetos WebView operan en modo de varios procesos, tienes cierta flexibilidad en cuanto a la manera en que tu app maneja las situaciones de falta de memoria. Puedes usar la API de Renderer Importance, que se introdujo en Android 8.0, a fin de establecer una política de prioridad para el procesador asignado a un objeto WebView específico. En particular, es posible que desees que la parte principal de tu app continúe ejecutándose cuando se cierre un procesador que muestra los objetos WebView de tu app. Puedes hacer esto, por ejemplo, si esperas no mostrar el objeto WebView durante mucho tiempo, de modo que el sistema pueda recuperar la memoria que estaba usando el procesador.

En el siguiente fragmento de código, se muestra cómo asignar una prioridad al proceso del procesador 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 en particular, la prioridad del procesador es la misma que la prioridad predeterminada de la app, o está vinculada a ella. El argumento true disminuye la prioridad del procesador a RENDERER_PRIORITY_WAIVED cuando el objeto WebView asociado ya no es visible. En otras palabras, un argumento true indica que a tu app no le importa si el sistema mantiene activo el proceso del procesador. De hecho, este nivel de prioridad más bajo hace que sea probable que el proceso del procesador se cierre en situaciones de falta de memoria.

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