WebView exécute du code natif sur plusieurs processus pour afficher du contenu Web dans votre application Android. Si vous ne gérez pas les instances WebView, cela peut entraîner des fuites de mémoire, des plantages dus à une mémoire insuffisante et une dégradation des performances de l'application.
Ce document explique le modèle de mémoire multiprocessus WebView, décrit comment gérer correctement son cycle de vie pour éviter les fuites et fournit des workflows pratiques pour diagnostiquer les problèmes de mémoire.
Comprendre l'architecture de la mémoire WebView
Pour gérer efficacement la mémoire WebView, comprenez comment Android alloue des ressources pour le contenu Web :
Exécution multiprocessus : sous Android 8.0 (niveau d'API 26) et versions ultérieures,
WebViewsépare le contenu Web des fonctions principales de votre application sur plusieurs processus (sur les appareils à faible RAM, il peut revenir à un seul processus) :- Processus hôte (navigateur) : processus d'application principal dans lequel s'exécutent votre code
Activityet votre code Java ou Kotlin. - Processus de rendu isolé : processus sandbox distinct (
SandboxedProcessService) qui analyse le code HTML et CSS, exécute JavaScript et affiche les pages Web.
- Processus hôte (navigateur) : processus d'application principal dans lequel s'exécutent votre code
Espace mémoire utilisé par la mémoire native : la majeure partie de l'
WebViewespace mémoire, y compris les graphiques rendus, l'arborescence DOM et l'espace mémoire d'exécution JavaScript, est allouée dans la mémoire native, et non dans le tas de mémoire Java. Une empreinte du tas de mémoire Java (.hprof) n'affiche qu'un objet wrapper Java léger et ne capture pas la mémoire réelle utilisée par le contenu Web.Impact de la mémoire native sur le système : contrairement aux allocations de tas de mémoire Java, qui sont limitées par la limite
maxHeapde l'application et échouent rapidement avec uneOutOfMemoryError, la mémoire native peut augmenter silencieusement jusqu'à atteindre plusieurs gigaoctets. Lorsque la mémoire native non libérée remplit la RAM physique et l'espace d'échange (zRAM), le Low Memory Killer (LMK) d'Android commence à arrêter les processus en arrière-plan pour récupérer de la mémoire. Cela dégrade le multitâche global de l'appareil avant de finalement arrêter l'application au premier plan.
Gérer le cycle de vie WebView
Une gestion appropriée du cycle de vie est essentielle pour éviter les fuites de mémoire. Une erreur courante consiste à supposer que la suppression d'un WebView de votre mise en page ou la fin automatique d'une Activity libère sa mémoire.
Pour garantir un nettoyage complet des références de contexte Java et des ressources de rendu natives, vous devez orchestrer explicitement une séquence de suppression dans le cycle de vie de votre composant hôte (tel que onDestroy()), en arrêtant l'exécution de la page active, en détachant la vue de son conteneur et en libérant les liaisons natives.
Nettoyer les instances WebView
Pour garantir un arrêt propre et libérer des ressources lorsque votre Activity ou Fragment est détruit, procédez comme suit :
- Supprimez le
WebViewde son conteneur parent (ViewGroup). - Arrêtez le chargement actif et effacez l'historique de navigation.
- Appelez
destroy(). - Effacez la référence à
null.
L'exemple suivant montre comment nettoyer correctement un WebView :
Kotlin
override fun onDestroy() { myWebView?.let { // Remove the WebView from its parent ViewGroup. (it.parent as? ViewGroup)?.removeView(it) // Stop active loading and clear history. it.stopLoading() it.clearHistory() // Destroy the instance. it.destroy() } myWebView = null super.onDestroy() }
Java
@Override
protected void onDestroy() {
if (myWebView != null) {
// Remove the WebView from its parent ViewGroup.
if (myWebView.getParent() instanceof ViewGroup) {
((ViewGroup) myWebView.getParent()).removeView(myWebView);
}
// Stop active loading and clear history.
myWebView.stopLoading();
myWebView.clearHistory();
// Destroy the instance.
myWebView.destroy();
}
myWebView = null;
super.onDestroy();
}
Comprendre la mémoire post-destruction
Lorsque vous appelez destroy(), le système libère le contexte Activity, nettoie les hiérarchies de vues et arrête le travail en arrière-plan du Web. Toutefois, vous remarquerez peut-être que la mémoire physique du processus (taille de l'ensemble résident) ne revient pas immédiatement à sa valeur de référence pré-WebView.
Ce comportement est normal. Les caches d'exécution natifs, les bibliothèques partagées et les pages de mémoire allouées restent résidents dans le processus jusqu'à ce que le système d'exploitation les récupère ou que le processus se termine. L'objectif principal de destroy() est d'empêcher les fuites de mémoire Activity cumulatives lorsque les utilisateurs naviguent dans les écrans Web.
Métriques de débogage clés
Lorsque vous analysez la consommation de mémoire WebView, concentrez-vous sur les métriques suivantes :
Taille de l'ensemble résident (RSS) : RAM physique totale mappée dans le processus, y compris le code et les bibliothèques partagés (libellés Total dans le Profileur Android Studio).
RSS anonyme (RssAnon) : mémoire allouée directement par le processus qui n'est pas sauvegardée par un fichier sur le disque (tel que le tas de mémoire natif et les allocations d'exécution JavaScript). Cela représente le coût de mémoire principal de votre contenu Web (libellé Alloué dans le Profileur Android Studio).
Empreinte de la mémoire privée (PMF) : somme du RSS anonyme et de l'espace d'échange (zRAM). Le PMF reflète la charge de mémoire réelle et inévitable que votre application impose au système.
PMF du navigateur par rapport au PMF du moteur de rendu : mémoire utilisée par le processus principal de votre application par rapport à la mémoire utilisée par le processus de rendu isolé. Le contenu Web lourd provoque des pics principalement dans le processus de rendu.
Nombre d'objets actifs (
WebViews,Activities,Views) : nombre d'instances actives d'UI, de contexte et deWebViewconservées en mémoire. Le suivi de ces éléments permet de déterminer si la croissance de la mémoire est due à des références Java conservées ou à des allocations uniquement natives.Autre mémoire privée et tas de mémoire natif : dans
dumpsys meminfo, les allocations C/C++ natives et les mappages de mémoire personnalisés (tels quePartitionAllocChromium ou les tas de mémoire d'exécution JavaScript intégrés) apparaissent sous Tas de mémoire natif et Autre mémoire privée plutôt que sous Tas de mémoire Java.
Pour en savoir plus sur les compteurs de mémoire de processus et leurs catégories, consultez le glossaire sur la mémoire de processus.
Workflows de diagnostic pratiques
Étant donné que WebView fonctionne sur plusieurs processus et alloue de la mémoire native, utilisez les outils et techniques suivants pour inspecter son empreinte :
Outils de profilage et de diagnostic
Pour inspecter les allocations de mémoire et diagnostiquer les fuites, utilisez les outils suivants :
Profileur de mémoire Android Studio : utilisez le Profileur de mémoire pour visualiser les allocations natives, suivre les catégories de mémoire au fil du temps et détecter les fuites
Activitylors des transitions d'écran.Suivi de la mémoire avec Perfetto : utilisez Perfetto pour enregistrer les compteurs de mémoire au niveau du système (tels que RSS et RSS anonyme) afin d'observer la croissance globale de la mémoire. Notez que les allocations du moteur natif
WebViewne produisent pas de piles d'appels dans l'outil de profilage du tas de mémoire de Perfetto. Utilisez les Outils pour les développeurs Chrome pour inspecter les instantanés du tas de mémoire JavaScript et les allocations DOM dans le contenu Web.
Inspecter le nombre d'objets actifs
Pour déterminer si la croissance de la mémoire est due à des objets de framework Java conservés (tels que des composants d’UI) ou à des allocations natives, inspectez la Objectssection de dumpsys meminfo :
adb shell dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"Le résultat affiche le nombre d'objets actifs :
Objects
Views: 142 ViewRootImpl: 1
AppContexts: 3 Activities: 1
Assets: 12 AssetManagers: 0
Local Binders: 32 Proxy Binders: 45
Parcel memory: 15 Parcel count: 30
Death Recipients: 2 WebViews: 1
Cette section affiche le nombre d'objets de framework actifs, de handles IPC et d'allocations Parcel. Pour les diagnostics WebView, concentrez-vous principalement sur Activities
et WebViews.
Effectuez l'interaction utilisateur cible (par exemple, ouvrir et fermer un écran Web) à plusieurs reprises et comparez les nombres :
Fuite d'instance : si
WebViewsouActivitiesaugmente à chaque navigation et ne revient pas à la valeur de référence, votre application fuit l'instance JavaWebViewou l'Activityhôte (par exemple, en raison d'unViewGroup.removeView()manquant ou de références d'écouteur conservées). Étant donné qu'uneActivityqui fuit épingle l'ensemble de son arborescence de vues et décode les ressources d'image en mémoire, les visites répétées épuisent rapidement le tas de mémoire Java et provoquent des plantagesOutOfMemoryError.Fuite native ou DOM : si
WebViewsetActivitiesrestent constants alors que le RSS total du processus et Autre mémoire privée continuent d'augmenter, la fuite provient de ressources natives non libérées, d'éléments DOM ou de liaisons de moteur JavaScript. Étant donné que ces allocations résident dans la mémoire native et contournent le récupérateur de mémoire ART, elles restent invisibles pour les outils de détection des fuites Java standards et continuent de s'accumuler jusqu'à ce que le système d'exploitation arrête l'application.
Profiler le processus de rendu isolé à l'aide de la CLI
L'exécution de dumpsys meminfo avec le nom de package de votre application ne génère que la mémoire du processus hôte principal. Pour inspecter le processus de rendu isolé dans lequel les pages Web sont affichées :
Recherchez l'ID de processus (PID) du service de rendu isolé :
adb shell dumpsys activity processes <var>PACKAGE_NAME</var> | grep "Isolated.*SandboxedProcessService"Le résultat affiche l'enregistrement du processus isolé et son PID RENDERER_PID (par exemple,
22155) :Isolated #5: ProcessRecord{... 22155:com.google.android.webview.debug:sandboxed_process0:...}Inspectez la répartition de la mémoire du processus de rendu à l'aide de son PID :
adb shell dumpsys meminfo <var>RENDERER_PID</var>Inspectez le processus de l'application hôte pour évaluer l'empreinte côté navigateur :
adb shell dumpsys meminfo <var>PACKAGE_NAME</var>
Inspecter les mappages et les allocations de mémoire
Pour voir quels sous-systèmes ou allocateurs natifs occupent la mémoire anonyme, inspectez les mappages de mémoire du processus :
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"Le tableau suivant répertorie les balises de mémoire anonymes courantes et leur pertinence pour la croissance de la mémoire :
| Balise de mémoire | Sous-système | Pertinence pour l'application et le contenu Web | Cause courante d'augmentation de la mémoire ? |
|---|---|---|---|
[anon:partition_alloc] |
PartitionAlloc Chromium | Allocations pour les arborescences DOM, les tampons de rendu, le tas de mémoire JavaScript V8 et l'exécution WebAssembly dans WebView. |
Oui (élevée) : le chargement de pages Web lourdes, de DOM riches en médias ou l'échec de l'appel de destroy() sur les instances WebView supprimées gonfle directement cette balise. |
[anon:scudo...] ou [anon:libc_malloc] |
Allocateurs de tas de mémoire natifs Android (Scudo / jemalloc) | Allocations natives C/C++ générales utilisées par les bibliothèques NDK, les ponts JNI et les pipelines graphiques natifs. | Oui (modérée à élevée) : la croissance se produit lorsque les wrappers JNI natifs ou les dépendances C++ tierces conservent des allocations non libérées lors des navigations. |
[anon:...] (par exemple, [anon:quickjs_heap...]) |
Scripts personnalisés ou environnements d'exécution natifs | Moteurs JavaScript intégrés, environnements d'exécution WebAssembly personnalisés ou pools de tampons natifs personnalisés. | Oui (dépend du contexte) : courant dans les applications hybrides qui exécutent des moteurs de script en parallèle des vues natives et ne parviennent pas à nettoyer les liaisons d'exécution. |
Limites des API de mémoire intégrées à l'application
Les API de mémoire intégrées à l'application (telles que Debug.getMemoryInfo ou
ActivityManager.getProcessMemoryInfo) ne mesurent que le processus appelant.
En mode multiprocessus, ces API ne peuvent pas capturer la mémoire consommée par le processus de rendu isolé. Pour une évaluation précise de la mémoire totale, utilisez des outils système tels que dumpsys meminfo, Perfetto ou Android Studio Profiler.
Triage en cas de mémoire élevée dans une application hybride
Lorsque vous diagnostiquez une croissance de mémoire inexpliquée lors d'interactions WebView récurrentes (telles que l'ouverture de liens Web ou la navigation dans des flux Web), utilisez le workflow de triage suivant pour déterminer si la fuite provient de la couche Java ou du moteur natif :
Isoler le type de fuite (Java ou natif) Exécutez
dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"avant et après des transitions utilisateur répétées (telles que l'ouverture et la fermeture d'articles Web ou le balayage de flux).- Observation : si le nombre de
Activitieset deWebViewsreste stable (par exemple, 1 à 2 instances actives), l'application ne fuit pas les contextesActivityni les instances JavaWebView.
- Observation : si le nombre de
Mesurer le delta de mémoire entre les interactions (suivi des séries temporelles) : capturez des instantanés
dumpsys meminfosur plusieurs interactions utilisateur pour calculer le taux d'allocation par transition :- Observation : le tas de mémoire Java reste limité et sain (pic pendant l'utilisation et baisse après la récupération de mémoire), mais Autre mémoire privée et Tas de mémoire natif augmentent régulièrement de plusieurs mégaoctets par transition. Cela prouve que la fuite se trouve entièrement dans la mémoire native en dehors de l'environnement d'exécution ART.
Les empreintes de tas de mémoire Java standards (
.hprof) n'afficheront aucun problème.
- Observation : le tas de mémoire Java reste limité et sain (pic pendant l'utilisation et baisse après la récupération de mémoire), mais Autre mémoire privée et Tas de mémoire natif augmentent régulièrement de plusieurs mégaoctets par transition. Cela prouve que la fuite se trouve entièrement dans la mémoire native en dehors de l'environnement d'exécution ART.
Les empreintes de tas de mémoire Java standards (
Inspecter les mappages de mémoire anonymes : examinez les mappages de mémoire du processus à l'aide d'ADB (consultez Inspecter les mappages et les allocations de mémoire) :
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"- Observation : la croissance de la mémoire est concentrée dans
[anon:partition_alloc]ou dans les tas de mémoire du moteur de script intégré, accompagnée d'une lente augmentation des références globales JNI. Cela indique que, bien que les vues Java aient été remplacées, les objets de page natifs sous-jacents ou les liaisons JavaScript n'ont pas été libérés.
- Observation : la croissance de la mémoire est concentrée dans
Résolution :
- Assurez-vous que chaque
WebViewrecyclé ou supprimé arrête explicitement les scripts actifs (stopLoading()), efface l'historique et appelledestroy(). - Supprimez les rappels de pont JavaScript personnalisés ou les références globales JNI associées aux vues ignorées.
- Vérifiez que
Private Otheret le RSS du processus se stabilisent après les transitions de navigation.
- Assurez-vous que chaque
Ressources supplémentaires
Pour en savoir plus sur le débogage et le profilage de la mémoire et des performances WebView, consultez les ressources suivantes :
- Gérer la mémoire de votre application
- Présentation de la gestion de la mémoire
- Déboguer des applications Web