Lorsque vous gérez le cycle de vie d'une application Android, la préservation de l'état de l'utilisateur lors de la récupération des ressources en arrière-plan est un élément essentiel d'une expérience utilisateur fluide. Pour les applications intégrant des workflows Web, WebView.saveState(Bundle)
vous permet de sérialiser l'historique de navigation et l'état d'une WebView dans un Bundle.
Ces données peuvent ensuite être restaurées à l'aide de WebView.restoreState(Bundle).
Toutefois, les implémentations standards peuvent être confrontées à des limites de taille des transactions lors de sessions de navigation intensives. Cette page décrit ces limites architecturales et fournit des stratégies pour éviter les exceptions liées à la mémoire tout en conservant l'historique de navigation.
Limite de transaction de 1 Mo et effacement de l'état
Android impose une limite stricte de 1 Mo sur le volume total de données pouvant être stockées dans savedInstanceState. Ce budget de 1 Mo est partagé entre l'ensemble du processus de l'application. Si une application intègre plusieurs instances WebView, leur état et leur historique de navigation collectifs doivent tenir dans cette seule allocation partagée. Le dépassement de cette limite déclenche une TransactionTooLargeException, ce qui entraîne un plantage de l'application.
Une stratégie d'atténuation courante, mais problématique, consiste à surveiller la taille du bundle d'état WebView et à effacer complètement l'historique WebView si elle dépasse un seuil de sécurité arbitraire (par exemple, 300 Ko). Bien que cela empêche un plantage, cela introduit de graves régressions dans l'expérience utilisateur :
Perte de la navigation vers l'arrière : Android met souvent fin aux processus d'application en arrière-plan pour récupérer de la mémoire pour d'autres tâches. Vous pouvez utiliser
saveState(Bundle)dans le rappel de cycle de vieonSaveInstanceState()pour conserver l'historique de navigation. Si vous effacez cet historique pour éviter la limite de transaction de 1 Mo, l'ensemble de la pile de navigation est perdu. Lorsque l'utilisateur revient à l'application, le bouton "Retour" du système quitte immédiatement le composant ou l'application, car aucun contexte historique ne permet de prendre en charge la navigation vers l'arrière, qu'un redémarrage du processus ait eu lieu ou non.Invalidation du BFCache : l'effacement de l'historique empêche l'application d'utiliser le cache Back-Forward (BFCache), ce qui supprime la possibilité d'afficher instantanément les pages précédemment visitées.
Latence accrue : les utilisateurs perdent leur état actuel dans la WebView, ce qui nécessite une nouvelle navigation et une réinitialisation complètes. Ce processus augmente considérablement la surcharge réseau et la latence des transactions.
Stratégies d'atténuation architecturales
Pour éviter les TransactionTooLargeException plantages sans dégrader l'
expérience utilisateur en supprimant complètement l'historique, vous devez maintenir un équilibre rigoureux
entre la conservation de l'état et l'efficacité de la mémoire. En mettant en œuvre les stratégies d'optimisation suivantes, vous pouvez gérer en toute sécurité le budget de transaction de 1 Mo tout en préservant l'historique de navigation essentiel et l'intégrité de la session.
Appliquer des limites de taille à la sérialisation de l'état
Au lieu d'effacer complètement la pile de navigation lorsqu'elle devient trop volumineuse, un modèle plus efficace consiste à tronquer les données historiques :
Règle de suppression ciblée : utilisez
WebViewCompat.saveState()pour sérialiser l'état tout en appliquant une limite d'octets spécifique (par exemple,WebViewCompat.saveState(webView, outState, maxSizeBytes)). Cette API supprime automatiquement les entrées de navigation les plus anciennes de manière séquentielle jusqu'à ce que la charge utile totale tienne dans l'allocation que vous avez définie. Il est essentiel de noter que cela ne tronque que leBundlesérialisé sans modifier ni effacer l'historique en direct duWebViewactif, ce qui garantit que la navigation vers l'arrière immédiate reste complètement intacte.Suppression des entrées suivantes : si l'interface de l'application fournit un bouton "Retour" mais pas de bouton de navigation vers l'avant dédié, vous pouvez ignorer toutes les entrées de navigation vers l'avant en définissant le paramètre
includeForwardStatede l'APIsaveStatesurfalse. Cela réduit considérablement la taille de la charge utile sans affecter les chemins de navigation disponibles pour l'utilisateur.
Gérer la latence des ressources avec l'API HTTP Cache Quota
Alors que saveState gère la limite de Bundle de 1 Mo pour l'historique de navigation éphémère, l'API HTTP Cache Quota permet de contrôler manuellement les ressources Web persistantes (cache du disque) pour chaque profil. Cela crée une distinction claire entre le contexte de navigation à court terme et les éléments mis en cache à long terme.
Le choix d'un quota approprié implique un compromis en termes de performances :
- Des quotas plus élevés améliorent la disponibilité hors connexion et la latence de chargement des ressources en conservant davantage d'éléments sur le disque.
- Des quotas plus faibles minimisent l'empreinte disque de l'application et empêchent l'OS d'évincer du cache d'autres données d'application critiques.
Ces paramètres sont conservés lors des redémarrages de l'application et doivent être configurés à partir du thread principal.
L'implémentation suivante montre comment configurer un quota de cache disque pour le profil par défaut :
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);
}
Pour en savoir plus sur les stratégies de dimensionnement des quotas, la gestion du cycle de vie et les limites de profil, consultez Gérer le quota de cache HTTP dans WebView.
Considérations clés sur les performances
Les points suivants mettent en évidence les limites techniques et les comportements des données internes qui régissent le comportement de l'état WebView :
Blobs
PageStateopaques : environ 70% des données stockées parsaveStatesont constituées de blobsPageStateinternes provenant du moteur de rendu. Ces données capturent les états de session granulaires, y compris les entrées de formulaire et les positions de défilement des iFrames. Évitez de tenter d'analyser ou de supprimer manuellement des segments individuels de ces blobs, car cela présente de graves risques de sécurité et compromet l'intégrité de la restauration de la session.Gestion granulaire de l'historique : l'API
WebBackForwardListstandard n'est pas compatible en mode natif avec la suppression arbitraire d'éléments historiques individuels. Pour une gestion stricte de l'état, vous devez implémenter des stratégies de troncature à l'aide des paramètresmaxSizeBytesetincludeForwardStatedansWebViewCompat.saveState()afin de garantir la sécurité architecturale.