WebViewCompat.navigate es una alternativa mejorada a WebView.loadUrl que proporciona un control detallado sobre la carga de páginas web, la administración del historial y el seguimiento del ciclo de vida de la navegación en WebView.
Anteriormente, iniciar la navegación de páginas con loadUrl tenía limitaciones notables:
- No se reemplazó la entrada del historial: No pudiste reemplazar la entrada del historial actual, por lo que no se puede navegar a una página nueva sin agregar una entrada a la pila de historial.
- Devoluciones de llamada desacopladas: No había ningún mecanismo directo para correlacionar una llamada
loadUrlespecífica con eventos de devolución de llamada posteriores enWebViewClient. - No se guardaron encabezados adicionales: Los encabezados personalizados que se pasaron a
loadUrlno se guardaron como parte del estado deWebView, por lo que se perdieron cuando se restableció el estado.
La API de WebViewCompat.navigate resuelve estos problemas con las siguientes funciones:
- Reemplazo de entrada del historial de navegación: Te permite reemplazar la página actual en el historial de la pila de
WebView. - Seguimiento de devoluciones de llamada correlacionadas: Devuelve un objeto
Navigationque sirve como identificador único en todas las etapas de un ciclo de vida de navegación. - Compatibilidad con encabezados de estado guardado: Los encabezados adicionales se guardan de forma confiable en el paquete de estado
WebViewpara que se puedan reutilizar cuando se restablezca el estado.
Funciones y limitaciones clave
Antes de adoptar WebViewCompat.navigate, considera las siguientes reglas y restricciones operativas:
Seguridad de subprocesos: Debes invocar
WebViewCompat.navigateen el subproceso de IU (principal).Cancelación y prioridad: Las navegaciones en curso no se pueden cancelar de forma explícita. Sin embargo, iniciar una nueva llamada de
navigateen el mismoWebViewreemplaza cualquier navegación activa.Compatibilidad con esquemas de URI: Se admiten esquemas de URI estándar (como
https:yhttp:) y personalizados. No se admite el esquemajavascript:.Límite de tamaño de la URL: La longitud máxima admitida de la cadena de URL es de 2 MB.
Verificación de funciones: Siempre verifica la disponibilidad de las funciones con
WebViewFeature.isFeatureSupportedantes de invocar la API para mantener la compatibilidad en diferentes versiones del APK de WebView.
Inicia la navegación y haz un seguimiento del ciclo de vida
Para configurar la navegación y hacer un seguimiento de su ciclo de vida, haz lo siguiente:
- Registra una implementación de
NavigationListenerconWebViewCompat.addNavigationListenerdurante la configuración deWebViewpara recibir devoluciones de llamada estructuradas del ciclo de vida. Registra el objeto de escucha una vez (en lugar de en cada llamada de navegación) para evitar pérdidas de memoria y ejecuciones duplicadas de devoluciones de llamada. - Construye una instancia de
NavigationParametersconNavigationParameters.Builderpara especificar comportamientos opcionales, como el reemplazo del historial o encabezados HTTP personalizados. - Llama a
WebViewCompat.navigatey pasa tu instancia deWebView, la URL de destino y los parámetros.
WebViewCompat.navigate devuelve un objeto Navigation que identifica la solicitud de forma única. En tus devoluciones de llamada de NavigationListener, compara este objeto con el parámetro Navigation entrante para hacer un seguimiento de esa navegación específica.
Ejemplo de implementación
En el siguiente ejemplo, se muestra cómo configurar parámetros de navegación, invocar WebViewCompat.navigate y escuchar eventos del ciclo de vida de la navegación:
Kotlin
class WebNavigationManager(private val webView: WebView) {
// Track the navigation instance returned by the API
private var currentNavigation: Navigation? = null
init {
// 1. Define listener to observe navigation lifecycle events
val listener = object : NavigationListener {
override fun onNavigationStarted(navigation: Navigation) {
if (navigation == currentNavigation) {
// Navigation started
}
}
override fun onNavigationRedirected(navigation: Navigation) {
if (navigation == currentNavigation) {
// Navigation encountered a redirect
}
}
override fun onNavigationCompleted(navigation: Navigation) {
if (navigation == currentNavigation) {
if (navigation.didCommit()) {
// Navigation committed successfully
} else if (navigation.didCommitErrorPage()) {
// Navigation committed an error page
val statusCode = navigation.statusCode
val error = navigation.webResourceError
}
}
}
override fun onFirstContentfulPaintMillis(page: Page, durationMillis: Long) {
// Match page with current navigation
if (page == currentNavigation?.page) {
// Page rendering started (First Contentful Paint achieved)
}
}
}
// 2. Register listener on the main thread
WebViewCompat.addNavigationListener(webView, listener)
}
@UiThread
fun navigateToPage(url: String) {
// Check feature availability
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
// Fall back to standard loadUrl if navigate API is unavailable
webView.loadUrl(url)
return
}
// 3. Configure navigation parameters
val params = NavigationParameters.Builder()
.setShouldReplaceCurrentEntry(true)
.addAdditionalHeaders(
mapOf("X-Test-Navigate-Header" to "TestValue")
)
.build()
// 4. Initiate navigation on the UI thread
currentNavigation = WebViewCompat.navigate(webView, url, params)
}
}
Java
public class WebNavigationManager {
private Navigation mCurrentNavigation;
private final WebView mWebView;
public WebNavigationManager(@NonNull WebView webView) {
mWebView = webView;
setupListener();
}
private void setupListener() {
// 1. Define listener to observe navigation lifecycle events
NavigationListener listener = new NavigationListener() {
@Override
public void onNavigationStarted(@NonNull Navigation navigation) {
if (navigation.equals(mCurrentNavigation)) {
// Navigation started
}
}
@Override
public void onNavigationRedirected(@NonNull Navigation navigation) {
if (navigation.equals(mCurrentNavigation)) {
// Navigation encountered a redirect
}
}
@Override
public void onNavigationCompleted(@NonNull Navigation navigation) {
if (navigation.equals(mCurrentNavigation)) {
if (navigation.didCommit()) {
// Navigation committed successfully
} else if (navigation.didCommitErrorPage()) {
// Navigation committed an error page
int statusCode = navigation.getStatusCode();
WebResourceErrorCompat error = navigation.getWebResourceError();
}
}
}
@Override
public void onFirstContentfulPaintMillis(@NonNull Page page, long durationMillis) {
if (mCurrentNavigation != null && page.equals(mCurrentNavigation.getPage())) {
// Page rendering started (First Contentful Paint achieved)
}
}
};
// 2. Register listener on the main thread
WebViewCompat.addNavigationListener(mWebView, listener);
}
@UiThread
public void navigateToPage(@NonNull String url) {
// Check feature availability
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
// Fall back to standard loadUrl if navigate API is unavailable
mWebView.loadUrl(url);
return;
}
// 3. Configure navigation parameters
NavigationParameters params = new NavigationParameters.Builder()
.setShouldReplaceCurrentEntry(true)
.addAdditionalHeaders(Collections.singletonMap(
"X-Test-Navigate-Header", "TestValue"
))
.build();
// 4. Initiate navigation on the UI thread
mCurrentNavigation = WebViewCompat.navigate(mWebView, url, params);
}
}
Propaga el estado de la app con encabezados HTTP
Las apps web suelen requerir contexto de la app para Android host para coordinar la lógica de backend o personalizar el contenido web. Agregar parámetros de consulta a la URL para pasar esta información puede sobrecargar las URLs, interferir con el almacenamiento en caché y exponer el estado interno de la app.
En cambio, te recomendamos que pases el contexto de la app con encabezados HTTP personalizados. Si usas WebViewCompat.navigate y NavigationParameters, puedes enviar estos datos de forma segura a tu servidor. Además, WebView conserva estos encabezados durante la restauración del estado, lo que garantiza que el contenido web siga siendo coherente en los cambios de configuración. Ten en cuenta que esta persistencia solo se aplica cuando se usa WebViewCompat.navigate. Si usas WebView.loadUrl, los encabezados personalizados no se guardan en el paquete de estado WebView y se pierden cuando se restablecen.
Casos de uso habituales
Estos son algunos casos de uso comunes para pasar el contexto de la app host:
- Versión de la app (
X-App-Version): Pasar la versión de lanzamiento de la app host (comoBuildConfig.VERSION_NAME) ayuda a tu servidor de backend a verificar la compatibilidad del puente de JavaScript nativo, controlar el acceso a las funciones o solicitar a los usuarios que actualicen apps más antiguas. - Plataforma del cliente (
X-Client-Platform): Identificar de forma explícita el entorno host como Android permite que el servidor entregue vínculos a la tienda de rutas o IU adaptadas a la plataforma sin depender del análisis de la cadenaUser-Agent.
Ejemplo de implementación
En el siguiente ejemplo, se muestra cómo pasar la versión de la aplicación y la plataforma del cliente a un servidor web:
Kotlin
// Attach host app metadata so the server can verify compatibility and tailor content
val params = NavigationParameters.Builder()
.addAdditionalHeaders(
mapOf(
"X-App-Version" to BuildConfig.VERSION_NAME,
"X-Client-Platform" to "Android"
)
)
.build()
// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", params)
Java
// Attach host app metadata so the server can verify compatibility and tailor content
Map<String, String> headers = new HashMap<>();
headers.put("X-App-Version", BuildConfig.VERSION_NAME);
headers.put("X-Client-Platform", "Android");
NavigationParameters params = new NavigationParameters.Builder()
.addAdditionalHeaders(headers)
.build();
// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", params);
Modos de falla y manejo de errores
La API de WebViewCompat.navigate proporciona mecanismos distintos para controlar los errores de configuración y las fallas de navegación en el tiempo de ejecución:
Excepciones de argumentos no válidos
Si se pasan argumentos no válidos, se activa un IllegalArgumentException síncrono.
Entre las causas comunes, se incluyen las siguientes:
- Se pasa
nullpara los parámetros obligatorios no nulos (webView,urloparams). - Se proporcionó un esquema de URL no admitido, como
javascript:. - Se pasan claves o valores de encabezado HTTP con formato incorrecto que no cumplen con las especificaciones de RFC 2616.
Errores en el proceso de navegación
Si se produce una falla durante la solicitud de red o la carga de la página (como un código de estado HTTP 404, una falla en la resolución de DNS o un error de SSL), WebViewCompat.navigate aún devuelve un objeto Navigation válido.
Cuando finalice la navegación, inspecciona los siguientes métodos en la instancia de Navigation dentro de la devolución de llamada de onNavigationCompleted para diagnosticar la falla:
getStatusCode: Devuelve el código de estado de la respuesta HTTP (por ejemplo,404o500).getWebResourceError: Devuelve un objetoWebResourceErrorCompatque detalla los errores de red, como los tiempos de espera de conexión o las fallas en la búsqueda de host.didCommitErrorPage: Indica siWebViewconfirmó y mostró una página de error al usuario.didCommit: Indica si la navegación se confirmó correctamente en una página de destino sin que se anulara.
Administración de paquetes de estado guardado
Cuando pasas encabezados adicionales con NavigationParameters, WebView guarda estos encabezados en su paquete de estado guardado para que se puedan reutilizar cuando se restablezca el estado. Sin embargo, las colecciones grandes de encabezados pueden aumentar considerablemente el tamaño del estado guardado Bundle.
Si necesitas restringir el tamaño del paquete para evitar TransactionTooLargeException durante los guardados de estado de Android, usa WebViewCompat.saveState. Este método te permite establecer un límite de tamaño máximo del paquete en bytes y, de manera opcional, excluir los elementos del historial de avance:
Kotlin
// Save state with a maximum bundle size limit (for example, 64 KB)
val maxSizeBytes = 64 * 1024
val includeForwardState = false
val outState = Bundle()
WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState)
Java
// Save state with a maximum bundle size limit (for example, 64 KB)
int maxSizeBytes = 64 * 1024;
boolean includeForwardState = false;
Bundle outState = new Bundle();
WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState);
El paquete resultante sigue siendo compatible con el método WebView.restoreState estándar.
Recomendaciones de migración e implementación
Para garantizar un rendimiento y una estabilidad óptimos cuando navegues en WebView, sigue estas recomendaciones:
Migra de
loadUrlanavigate: Migra todas las llamadas heredadas deWebView.loadUrlaWebViewCompat.navigate. Esto garantiza una administración uniforme del historial y que los encabezados siempre se guarden como parte del estado guardado.Siempre verifica la compatibilidad con la función: Antes de invocar la API, confirma la compatibilidad en el tiempo de ejecución con
WebViewFeature.isFeatureSupportedpara protegerte contra versiones anteriores de WebView.Correlaciona instancias de navegación: Usa el objeto
Navigationque se devolvió para diferenciar las navegaciones simultáneas o filtrar las devoluciones de llamada cuando administres varias instancias deWebView.Registra el objeto de escucha una vez durante la inicialización: Dado que
WebViewCompat.addNavigationListeneragrega un objeto de escucha en lugar de reemplazar uno existente, registra tuNavigationListeneruna vez durante la configuración deWebViewpara evitar pérdidas de memoria y ejecuciones duplicadas de devoluciones de llamada en navegaciones posteriores.Supervisa el tamaño del estado de guardado: Cuando pases cargas útiles de encabezado grandes, usa
WebViewCompat.saveStatecon límites de tamaño explícitos para evitar guardar datos de estado excesivos.
Recursos adicionales
Para obtener más información sobre las capacidades web integradas y la optimización del rendimiento, consulta las siguientes guías:
- Simplifica tu implementación de WebView con Jetpack Webkit
- Carga especulativa en WebView
- Optimiza el inicio de WebView
- Cómo controlar la finalización del proceso del renderizador de WebView