Cuando se administra el ciclo de vida de una app para Android, preservar el estado del usuario durante la recuperación de recursos en segundo plano es un componente fundamental de una experiencia del usuario fluida. En el caso de las apps que incorporan flujos de trabajo web, WebView.saveState(Bundle)
te permite serializar el historial de navegación y el estado de WebView en un Bundle.
Posteriormente, estos datos se pueden restablecer con WebView.restoreState(Bundle).
Sin embargo, las implementaciones estándar pueden tener limitaciones de tamaño de transacción en sesiones de navegación pesadas. En esta página, se describen estas limitaciones arquitectónicas y se proporcionan estrategias para evitar excepciones relacionadas con la memoria mientras se mantiene el historial de navegación.
El límite de transacción de 1 MB y el borrado de estado
Android impone un límite estricto de 1 MB en el volumen total de datos que se pueden almacenar en savedInstanceState. Este presupuesto de 1 MB se comparte en todo el proceso de la app. Si una app incorpora varias instancias de WebView, su estado e historial de navegación colectivos deben caber dentro de esta única asignación compartida. Si se supera este límite, se activa una TransactionTooLargeException, lo que provoca que la app falle.
Una estrategia de mitigación común, pero problemática, consiste en supervisar el tamaño del paquete de estado de WebView y borrar por completo el historial de WebView si cruza un umbral de seguridad arbitrario (como 300 KB). Si bien esto evita una falla, introduce regresiones graves en la experiencia del usuario:
Pérdida de navegación hacia atrás: Android suele finalizar los procesos de apps en segundo plano para recuperar memoria para otras tareas. Puedes usar
saveState(Bundle)dentro de la devolución de llamada del ciclo de vidaonSaveInstanceState()para preservar el historial de navegación. Si borras este historial para evitar el límite de transacción de 1 MB, se pierde toda la pila de navegación. Cuando el usuario regresa a la app, el botón Atrás del sistema sale de inmediato del componente o de la app porque no queda contexto histórico para admitir la navegación hacia atrás, independientemente de si se produjo un reinicio del proceso.Invalidación de BFCache: Borrar el historial impide que la app use la caché de atrás y adelante (BFCache), lo que quita la capacidad de renderizar instantáneamente las páginas visitadas anteriormente.
Mayor latencia: Los usuarios pierden su estado actual en WebView, lo que requiere una nueva navegación y una reinicialización completas. Este proceso aumenta significativamente la sobrecarga de la red y la latencia de las transacciones.
Estrategias de mitigación arquitectónica
Para evitar fallas de TransactionTooLargeException sin degradar la
experiencia del usuario mediante la eliminación completa del historial, debes mantener un equilibrio riguroso
entre la retención de estado y la eficiencia de la memoria. Si implementas las siguientes estrategias de optimización, puedes administrar de forma segura el presupuesto de transacción de 1 MB mientras preservas el historial de navegación esencial y la integridad de la sesión.
Aplica límites de tamaño en la serialización de estado
En lugar de borrar por completo la pila de navegación cuando crece demasiado, un patrón más eficaz es truncar los datos históricos:
Política de eliminación segmentada: Usa
WebViewCompat.saveState()para serializar el estado mientras aplicas un límite de bytes específico (por ejemplo,WebViewCompat.saveState(webView, outState, maxSizeBytes)). Esta API quita automáticamente las entradas de navegación más antiguas de forma secuencial hasta que la carga útil total quepa dentro de la asignación definida. Es fundamental que esto solo trunque elBundleserializado sin modificar ni borrar el historial en vivo delWebViewactivo, lo que garantiza que la navegación hacia atrás inmediata permanezca completamente intacta.Eliminación de entradas hacia adelante: Si la interfaz de la aplicación proporciona un botón Atrás pero no tiene un botón de navegación hacia adelante dedicado, puedes descartar todas las entradas de navegación hacia adelante configurando el parámetro
includeForwardStatede la API desaveStateenfalse. Esto reduce significativamente el tamaño de la carga útil sin afectar las rutas de navegación disponibles del usuario.
Administra la latencia de recursos con la API de HTTP Cache Quota
Si bien saveState administra el límite de Bundle de 1 MB para el historial de navegación efímero, la API de HTTP Cache Quota proporciona control manual sobre los recursos web persistentes (memoria caché del disco) por perfil. Esto crea una distinción clara entre el contexto de navegación a corto plazo y los recursos almacenados en caché a largo plazo.
Elegir una cuota adecuada implica una compensación de rendimiento:
- Las cuotas más altas mejoran la disponibilidad sin conexión y la latencia de carga de recursos, ya que mantienen más recursos en el disco.
- Las cuotas más bajas minimizan el espacio en disco de la app y evitan el desalojo de la caché dirigida por el SO de otros datos de app críticos.
Estos parámetros de configuración persisten después de que se reinicia la app y se deben configurar desde el subproceso principal.
En la siguiente implementación, se muestra cómo configurar una cuota de memoria caché del disco para el perfil predeterminado:
Kotlin
if (WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) &&
WebViewFeature.isFeatureSupported(WebViewFeature.HTTP_CACHE)) {
val defaultProfile = ProfileStore.getInstance()
.getOrCreateProfile(Profile.DEFAULT_PROFILE_NAME)
val httpCache = defaultProfile.httpCache
// Set explicit cache size to 50MB (50 * 1024 * 1024 bytes)
httpCache.setQuotaBytes(50L * 1024 * 1024)
}
Java
if (WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) &&
WebViewFeature.isFeatureSupported(WebViewFeature.HTTP_CACHE)) {
Profile defaultProfile = ProfileStore.getInstance()
.getOrCreateProfile(Profile.DEFAULT_PROFILE_NAME);
HttpCache httpCache = defaultProfile.getHttpCache();
// Set explicit cache size to 50MB (50 * 1024 * 1024 bytes)
httpCache.setQuotaBytes(50L * 1024 * 1024);
}
Para obtener más información sobre las estrategias de ajuste de tamaño de cuotas, la administración del ciclo de vida y los límites de perfil, consulta Administra la cuota de caché HTTP en WebView.
Consideraciones clave sobre el rendimiento
En los siguientes puntos, se destacan las limitaciones técnicas y los comportamientos de datos internos que rigen el comportamiento del estado de WebView:
Blobs
PageStateopacos: Aproximadamente el 70% de los datos almacenados porsaveStateconsta de blobsPageStateinternos del motor de renderización. Estos datos capturan estados de sesión detallados, incluidas las entradas de formularios y las posiciones de desplazamiento de iframe. Evita intentar analizar o quitar manualmente segmentos individuales de estos blobs, ya que hacerlo plantea graves riesgos de seguridad y rompe la integridad de la restauración de la sesión.Administración de historial detallada: La API estándar
WebBackForwardListno admite de forma nativa la eliminación arbitraria de elementos históricos individuales. Para una administración de estado estricta, debes implementar estrategias de truncamiento con los parámetrosmaxSizeBytesyincludeForwardStatedentro deWebViewCompat.saveState()para garantizar la seguridad arquitectónica.