WebView führt nativen Code in mehreren Prozessen aus, um Webinhalte in Ihrer Android-App zu rendern. Wenn WebView-Instanzen nicht verwaltet werden, kann dies zu Speicherlecks, Abstürzen aufgrund von Arbeitsspeichermangel und einer schlechteren App-Leistung führen.
In diesem Dokument wird das WebView-Speichermodell mit mehreren Prozessen erläutert. Außerdem wird beschrieben, wie Sie den Lebenszyklus richtig verwalten, um Lecks zu vermeiden, und es werden praktische Workflows zur Diagnose von Speicherproblemen bereitgestellt.
Speicherarchitektur von WebView
Um den WebView-Speicher effektiv zu verwalten, müssen Sie wissen, wie Android Ressourcen für Webinhalte zuweist:
Ausführung in mehreren Prozessen:Unter Android 8.0 (API-Level 26) und höher trennt
WebViewWeb-Inhalte von den Kernfunktionen Ihrer App in mehreren Prozessen (auf Geräten mit wenig RAM wird möglicherweise auf einen einzelnen Prozess zurückgegriffen):- Hostprozess (Browserprozess):Der Haupt-App-Prozess, in dem Ihr
Activity- und Java- oder Kotlin-Code ausgeführt wird. - Isolierter Renderer-Prozess:Ein separater Sandbox-Prozess (
SandboxedProcessService), in dem HTML und CSS geparst, JavaScript ausgeführt und Webseiten gerendert werden.
- Hostprozess (Browserprozess):Der Haupt-App-Prozess, in dem Ihr
Nativer Speicherbedarf:Der größte Teil des
WebView-Speichers, einschließlich gerenderter Grafiken, des DOM-Baums und des JavaScript-Laufzeitspeichers, wird im nativen Speicher und nicht im Java-Heap zugewiesen. Ein Java-Heap-Dump (.hprof) zeigt nur ein einfaches Java-Wrapper-Objekt und nicht den tatsächlichen Arbeitsspeicher, der von Webinhalten verwendet wird.Systemauswirkungen von nativem Arbeitsspeicher:Im Gegensatz zu Java-Heap-Zuweisungen, die durch das
maxHeap-Limit der App begrenzt sind und schnell mit einemOutOfMemoryErrorfehlschlagen, kann der native Arbeitsspeicher unbemerkt auf Gigabyte anwachsen. Wenn nicht freigegebener nativer Arbeitsspeicher den physischen Arbeitsspeicher und den Auslagerungsspeicher (zRAM) füllt, beginnt der Low Memory Killer (LMK) von Android, Hintergrundprozesse zu beenden, um Arbeitsspeicher freizugeben. Dadurch wird das Multitasking auf dem Gerät insgesamt beeinträchtigt, bevor die App im Vordergrund beendet wird.
WebView-Lebenszyklus verwalten
Eine ordnungsgemäße Lebenszyklusverwaltung ist entscheidend, um Speicherlecks zu vermeiden. Ein häufiger Fehler ist die Annahme, dass durch das Entfernen eines WebView aus dem Layout oder das automatische Beenden eines Activity der Speicher freigegeben wird.
Damit sowohl Java-Kontextreferenzen als auch native Renderressourcen vollständig bereinigt werden, müssen Sie explizit eine Abbaufolge im Lebenszyklus der Hostkomponente (z. B. onDestroy()) orchestrieren. Dabei müssen Sie die aktive Seitenausführung beenden, die Ansicht von ihrem Container trennen und native Bindungen freigeben.
WebView-Instanzen bereinigen
Damit das Herunterfahren sauber erfolgt und Ressourcen freigegeben werden, wenn Ihre Activity oder Fragment zerstört wird, gehen Sie so vor:
- Entfernen Sie
WebViewaus dem übergeordneten Container (ViewGroup). - Aktives Laden beenden und Navigationsverlauf löschen.
- Rufen Sie
destroy()an. - Entfernen Sie den Verweis auf
null.
Das folgende Beispiel zeigt, wie eine WebView richtig bereinigt wird:
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();
}
Grundlegendes zum Speicher nach der Vernichtung
Wenn Sie destroy() aufrufen, gibt das System den Activity-Kontext frei, bereinigt die Ansichtshierarchien und beendet die Web-Hintergrundarbeit. Möglicherweise stellen Sie jedoch fest, dass der physische Speicher des Prozesses (Resident Set Size) nicht sofort auf den Pre-WebView-Ausgangswert sinkt.
Das ist aber normal. Native Laufzeit-Caches, gemeinsam genutzte Bibliotheken und zugewiesene Speicherseiten bleiben im Prozess, bis das Betriebssystem sie zurückfordert oder der Prozess beendet wird. Das primäre Ziel von destroy() ist es, kumulative Activity-Speicherlecks zu verhindern, wenn Nutzer auf webbasierten Bildschirmen navigieren.
Wichtige Messwerte für das Debugging
Konzentrieren Sie sich bei der Analyse des WebView-Speicherverbrauchs auf die folgenden Messwerte:
Resident Set Size (RSS): Der gesamte physische RAM, der dem Prozess zugeordnet ist, einschließlich gemeinsam genutzten Codes und Bibliotheken (im Android Studio Profiler als Total gekennzeichnet).
Anonymer RSS (RssAnon): Speicher, der direkt vom Prozess zugewiesen wird und nicht durch eine Datei auf der Festplatte gesichert wird, z. B. Zuweisungen für den nativen Heap und die JavaScript-Laufzeit. Dies sind die primären Speicherkosten Ihrer Webinhalte (im Android Studio Profiler als Allocated gekennzeichnet).
Privater Speicherbedarf (Private Memory Footprint, PMF): Die Summe aus anonymer RSS und Auslagerung (zRAM). Der PMF gibt die tatsächliche Arbeitsspeicherbelastung an, die Ihre App für das System darstellt und die nicht ausgelagert werden kann.
Browser-PMF im Vergleich zu Renderer-PMF:Arbeitsspeicher, der vom Hauptprozess Ihrer App verwendet wird, im Vergleich zu Arbeitsspeicher, der vom isolierten Renderer-Prozess verwendet wird. Umfangreiche Webinhalte verursachen vor allem im Renderer-Prozess Spitzen.
Live Object Counts (
WebViews,Activities,Views): Die Anzahl der aktiven UI-, Kontext- undWebView-Instanzen, die im Arbeitsspeicher gehalten werden. Durch das Tracking dieser Werte lässt sich feststellen, ob der Speicherzuwachs durch beibehaltene Java-Referenzen oder nur durch native Zuweisungen verursacht wird.Privater „Other“-Speicher und nativer Heap:In
dumpsys meminfowerden native C/C++-Zuweisungen und benutzerdefinierte Speicherzuordnungen (z. B. ChromiumPartitionAllocoder eingebettete JavaScript-Laufzeit-Heaps) unter „Nativer Heap“ und „Privater Other“-Speicher anstatt unter „Java-Heap“ angezeigt.
Weitere Informationen zu Prozessspeicherzählern und ihren Kategorien finden Sie im Glossar zum Prozessspeicher.
Praktische Diagnose-Workflows
Da WebView in mehreren Prozessen ausgeführt wird und nativen Speicher zuweist, können Sie den Speicherbedarf mit den folgenden Tools und Techniken untersuchen:
Tools zur Profilerstellung und Diagnose
Verwenden Sie die folgenden Tools, um Speicherzuweisungen zu prüfen und Lecks zu diagnostizieren:
Speicher-Profiler von Android Studio:Mit dem Speicher-Profiler können Sie native Zuweisungen visualisieren, Arbeitsspeicherkategorien im Zeitverlauf verfolgen und
Activity-Leaks bei Bildschirmübergängen erkennen.Arbeitsspeicher mit Perfetto im Blick behalten:Verwenden Sie Perfetto, um Arbeitsspeicherzähler auf Systemebene (z. B. RSS und anonyme RSS) aufzuzeichnen und so das gesamte Arbeitsspeicherwachstum zu beobachten. Bei
WebView-Zuweisungen der nativen Engine werden im Heap-Profilerstellungstool von Perfetto keine Callstacks erstellt. Verwenden Sie die Chrome-Entwicklertools, um JavaScript-Heap-Snapshots und DOM-Zuweisungen in den Webinhalten zu untersuchen.
Anzahl der Live-Objekte prüfen
Um festzustellen, ob das Speicherwachstum durch beibehaltene Java-Framework-Objekte (z. B. UI-Komponenten) oder native Zuweisungen verursacht wird, sehen Sie sich den Bereich Objects von dumpsys meminfo an:
adb shell dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"In der Ausgabe werden die Anzahl der Live-Objekte angezeigt:
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
In diesem Abschnitt werden die Anzahl der aktiven Framework-Objekte, IPC-Handles und Parcel-Zuweisungen angezeigt. Konzentrieren Sie sich bei der WebView-Diagnose hauptsächlich auf Activities und WebViews.
Führen Sie die Zielnutzerinteraktion (z. B. Öffnen und Schließen eines Webbildschirms) wiederholt aus und vergleichen Sie die Zählungen:
Instanz-Leak:Wenn
WebViewsoderActivitiesbei jeder Navigation ansteigt und nicht zum Ausgangswert zurückkehrt, gibt es in Ihrer App einen Leak der Java-WebView-Instanz oder des HostsActivity(z. B. aufgrund eines fehlendenViewGroup.removeView()oder beibehaltener Listener-Referenzen). Da ein Leak vonActivityden gesamten Ansichtsbaum und die decodierten Bildressourcen im Arbeitsspeicher fixiert, wird der Java-Heap bei wiederholten Besuchen schnell erschöpft und es kommt zuOutOfMemoryError-Abstürzen.Native- oder DOM-Leck: Wenn
WebViewsundActivitieskonstant bleiben, während die gesamte Prozess-RSS und Private Other weiter ansteigen, stammt das Leck von nicht freigegebenen nativen Ressourcen, DOM-Elementen oder JavaScript-Engine-Bindungen. Da sich diese Zuweisungen im nativen Speicher befinden und den ART-Garbage Collector umgehen, sind sie für Standardtools zur Erkennung von Java-Speicherlecks unsichtbar und werden immer mehr, bis das Betriebssystem die App beendet.
Isolierten Renderer-Prozess mit der CLI profilieren
Wenn Sie dumpsys meminfo mit dem Paketnamen Ihrer App ausführen, wird nur der Arbeitsspeicher für den Haupt-Hostprozess ausgegeben. So prüfen Sie den isolierten Renderingprozess, in dem Webseiten gerendert werden:
Suchen Sie die Prozess-ID (PID) des isolierten Renderer-Dienstes:
adb shell dumpsys activity processes <var>PACKAGE_NAME</var> | grep "Isolated.*SandboxedProcessService"In der Ausgabe wird der isolierte Prozessdatensatz und seine PID RENDERER_PID (z. B.
22155) angezeigt:Isolated #5: ProcessRecord{... 22155:com.google.android.webview.debug:sandboxed_process0:...}Sehen Sie sich die Arbeitsspeicheraufschlüsselung des Renderer-Prozesses anhand seiner PID an:
adb shell dumpsys meminfo <var>RENDERER_PID</var>Untersuchen Sie den Host-App-Prozess, um die Auswirkungen auf den Browser zu bewerten:
adb shell dumpsys meminfo <var>PACKAGE_NAME</var>
Arbeitsspeicherzuordnungen und -belegungen prüfen
Um herauszufinden, welche nativen Subsysteme oder Zuweisungen anonymen Speicher belegen, sehen Sie sich die Prozessspeicherkarten an:
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"In der folgenden Tabelle sind häufige anonyme Arbeitsspeicher-Tags und ihre Relevanz für die Arbeitsspeichererweiterung aufgeführt:
| Erinnerungs-Tag | Subsystem | Relevanz für App- und Webinhalte | Häufige Ursache für erhöhten Arbeitsspeicher? |
|---|---|---|---|
[anon:partition_alloc] |
Chromium PartitionAlloc | Zuweisungen für DOM-Bäume, Rendering-Puffer, V8-JavaScript-Heap und WebAssembly-Ausführung in WebView. |
Ja (Hoch): Das Laden umfangreicher Webseiten, DOMs mit vielen Medien oder das Versäumnis, destroy() für verworfene WebView-Instanzen direkt aufzurufen, führt zu einer Erhöhung dieses Tags. |
[anon:scudo...] oder [anon:libc_malloc] |
Native Heap-Zuweisungen für Android (Scudo / jemalloc) | Allgemeine native C/C++-Zuweisungen, die von NDK-Bibliotheken, JNI-Bridges und nativen Grafikpipelines verwendet werden. | Ja (mittel bis hoch): Das Wachstum tritt auf, wenn native JNI-Wrapper oder C++-Drittanbieterabhängigkeiten nicht freigegebene Zuweisungen bei Navigationsvorgängen beibehalten. |
[anon:...] (z. B. [anon:quickjs_heap...]) |
Benutzerdefinierte Scripts oder native Laufzeiten | Eingebettete JavaScript-Engines, benutzerdefinierte WebAssembly-Laufzeitumgebungen oder benutzerdefinierte native Pufferpools. | Ja (kontextabhängig): Häufig bei Hybrid-Apps, die Scripting-Engines neben nativen Ansichten ausführen und Laufzeitbindungen nicht bereinigen. |
Einschränkungen der In-App-Speicher-APIs
In-App-Speicher-APIs (z. B. Debug.getMemoryInfo oder ActivityManager.getProcessMemoryInfo) messen nur den aufrufenden Prozess.
Im Mehrprozessmodus können diese APIs den vom isolierten Rendererprozess belegten Arbeitsspeicher nicht erfassen. Für eine genaue Bewertung des Gesamtspeichers sollten Sie Systemtools wie dumpsys meminfo, Perfetto oder Android Studio Profiler verwenden.
Hohe Arbeitsspeichernutzung in einer Hybrid-App untersuchen
Wenn Sie ein unerklärliches Anwachsen des Arbeitsspeichers bei wiederkehrenden WebView-Interaktionen (z. B. beim Öffnen von Weblinks oder beim Navigieren in webbasierten Feeds) diagnostizieren, verwenden Sie den folgenden Triage-Workflow, um zu ermitteln, ob das Leck in der Java-Ebene oder in der nativen Engine auftritt:
Art des Lecks isolieren (Java oder nativ): Führen Sie
dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"vor und nach wiederholten Nutzerübergängen aus, z. B. beim Öffnen und Schließen von Webartikeln oder beim Wischen durch Feeds.- Beobachtung:Wenn die Anzahl von
Activities- undWebViews-Zählern konstant bleibt (z. B. 1–2 aktive Instanzen), gibt es in der App keine Lecks beiActivity-Kontexten oder Java-WebView-Instanzen.
- Beobachtung:Wenn die Anzahl von
Speicherdelta über Interaktionen hinweg messen (Zeitreihen-Tracking): Erfassen Sie
dumpsys meminfo-Momentaufnahmen über mehrere Nutzerinteraktionen hinweg, um die Zuweisungsrate pro Übergang zu berechnen:- Beobachtung:Der Java-Heap bleibt begrenzt und in gutem Zustand (er steigt bei der Verwendung und sinkt nach der Garbage Collection), aber Private Other und Native Heap steigen bei jedem Übergang um mehrere Megabyte. Das beweist, dass sich das Speicherleck vollständig im nativen Speicher außerhalb der ART-Laufzeit befindet.
Standardmäßige Java-Heap-Dumps (
.hprof) zeigen keine Probleme.
- Beobachtung:Der Java-Heap bleibt begrenzt und in gutem Zustand (er steigt bei der Verwendung und sinkt nach der Garbage Collection), aber Private Other und Native Heap steigen bei jedem Übergang um mehrere Megabyte. Das beweist, dass sich das Speicherleck vollständig im nativen Speicher außerhalb der ART-Laufzeit befindet.
Standardmäßige Java-Heap-Dumps (
Anonyme Arbeitsspeicherzuordnungen prüfen:Untersuchen Sie die Arbeitsspeicherzuordnungen des Prozesses mit ADB (siehe Arbeitsspeicherzuordnungen und ‑zuweisungen prüfen):
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"- Beobachtung:Der Arbeitsspeicherverbrauch steigt hauptsächlich in den Heaps der
[anon:partition_alloc]- oder eingebetteten Scripting-Engine. Gleichzeitig steigt die Anzahl der globalen JNI-Referenzen langsam an. Das bedeutet, dass die Java-Ansichten zwar ersetzt wurden, die zugrunde liegenden nativen Seitenobjekte oder JavaScript-Bindungen jedoch nicht freigegeben wurden.
- Beobachtung:Der Arbeitsspeicherverbrauch steigt hauptsächlich in den Heaps der
Abhilfe:
- Achten Sie darauf, dass bei jedem recycelten oder entsorgten
WebViewaktive Skripts (stopLoading()) explizit beendet, der Verlauf gelöscht unddestroy()aufgerufen wird. - Rufen Sie benutzerdefinierte JavaScript-Bridge-Callbacks oder globale JNI-Referenzen auf, die mit geschlossenen Ansichten verknüpft sind.
- Prüfen Sie, ob
Private Otherund der Prozess-RSS nach Navigationsübergängen stabilisiert werden.
- Achten Sie darauf, dass bei jedem recycelten oder entsorgten
Zusätzliche Ressourcen
Weitere Informationen zum Debuggen und Profilieren von Arbeitsspeicher und WebView-Leistung finden Sie in den folgenden Ressourcen: