WebView-Speicher verwalten und analysieren

WebView führt nativen Code in mehreren Prozessen aus, um Webinhalte in Ihrer Android-App zu rendern. Wenn Sie WebView-Instanzen nicht verwalten, kann dies zu Speicherlecks, Abstürzen aufgrund von Arbeitsspeichermangel und einer schlechteren App-Leistung führen.

In diesem Dokument wird das WebView-Arbeitsspeichermodell für mehrere Prozesse erläutert. Außerdem wird beschrieben, wie Sie den Lebenszyklus richtig verwalten, um Speicherlecks zu vermeiden, und es werden praktische Workflows zur Diagnose von Arbeitsspeicherproblemen vorgestellt.

Arbeitsspeicherarchitektur von WebView

Um den WebView-Arbeitsspeicher 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 WebView Webinhalte von den Kernfunktionen Ihrer App in mehreren Prozessen. Auf Geräten mit wenig Arbeitsspeicher wird möglicherweise auf einen einzelnen Prozess zurückgegriffen:

    • Hostprozess (Browserprozess):Der Hauptprozess der App, in dem Ihr Activity- und Java- oder Kotlin-Code ausgeführt wird.
    • Isolierter Rendererprozess:Ein separater Sandbox-Prozess (SandboxedProcessService), der HTML und CSS parst, JavaScript ausführt und Webseiten rendert.
  • Nativer Speicherbedarf:Der größte Teil des WebView-Arbeitsspeichers, einschließlich gerenderter Grafiken, des DOM-Baums und des JavaScript-Laufzeitspeichers, wird im nativen Arbeitsspeicher und nicht im Java-Heap zugewiesen. Ein Java-Heap-Dump (.hprof) zeigt nur ein einfaches Java-Wrapperobjekt und erfasst nicht den tatsächlichen Arbeitsspeicher, der von Webinhalten verwendet wird.

  • Auswirkungen von nativem Arbeitsspeicher auf das System:Im Gegensatz zu Java-Heap-Zuweisungen, die durch das maxHeap-Limit der App begrenzt sind und bei einem OutOfMemoryError schnell fehlschlagen, kann der native Arbeitsspeicher unbemerkt auf Gigabytes anwachsen. Wenn nicht freigegebener nativer Arbeitsspeicher den physischen RAM und den Auslagerungsspeicher (zRAM) füllt, beendet der Low Memory Killer (LMK) von Android Hintergrundprozesse, um Arbeitsspeicher freizugeben. Dadurch wird das Multitasking des Geräts insgesamt beeinträchtigt, bevor die App im Vordergrund schließlich 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 Ihrem Layout oder das automatische Beenden einer Activity der Arbeitsspeicher freigegeben wird.

Um sowohl Java-Kontextreferenzen als auch native Renderressourcen vollständig zu bereinigen, müssen Sie explizit eine Teardown-Sequenz 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

So sorgen Sie für ein ordnungsgemäßes Herunterfahren und geben Ressourcen frei, wenn Ihre Activity oder Ihr Fragment zerstört wird:

  1. Entfernen Sie das WebView aus dem übergeordneten Container (ViewGroup).
  2. Beenden Sie das aktive Laden und löschen Sie den Navigationsverlauf.
  3. Rufen Sie destroy() auf.
  4. Löschen Sie die Referenz auf null.

Das folgende Beispiel zeigt, wie Sie ein WebView richtig bereinigen:

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();
}

Arbeitsspeicher nach der Zerstörung

Wenn Sie destroy() aufrufen, gibt das System den Activity-Kontext frei, bereinigt Ansichtshierarchien und beendet die Web-Hintergrundarbeit. Möglicherweise stellen Sie jedoch fest, dass der physische Arbeitsspeicher des Prozesses (Resident Set Size) nicht sofort auf den Ausgangswert vor `WebView` sinkt.

Das ist aber normal. Native Laufzeit-Caches, gemeinsam genutzte Bibliotheken und zugewiesene Arbeitsspeicherseiten bleiben im Prozess resident, bis das Betriebssystem sie freigibt oder der Prozess beendet wird. Das Hauptziel von destroy() ist es, kumulative Activity-Speicherlecks zu vermeiden, wenn Nutzer auf webbasierten Bildschirmen navigieren.

Wichtige Messwerte für die Fehlerbehebung

Konzentrieren Sie sich bei der Analyse des WebView-Arbeitsspeicherverbrauchs 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 bezeichnet).

  • Anonyme RSS (RssAnon) : Arbeitsspeicher, der direkt vom Prozess zugewiesen wird und nicht durch eine Datei auf der Festplatte gesichert ist (z. B. native Heap- und JavaScript-Laufzeitzuweisungen). Dies stellt die primären Arbeitsspeicherkosten Ihrer Webinhalte dar (im Android Studio Profiler als Allocated bezeichnet).

  • Private Memory Footprint (PMF) : Die Summe aus anonymer RSS und Auslagerung (zRAM). PMF spiegelt die tatsächliche, nicht auslagerbare Arbeitsspeicherbelastung wider, die Ihre App für das System darstellt.

  • Browser-PMF im Vergleich zu Renderer-PMF:Arbeitsspeicher, der vom Hauptprozess Ihrer App verwendet wird, im Vergleich zu Arbeitsspeicher, der vom isolierten Rendererprozess verwendet wird. Umfangreiche Webinhalte verursachen vor allem im Rendererprozess Spitzen.

  • Anzahl der Live-Objekte (WebViews, Activities, Views): Die Anzahl der aktiven UI-, Kontext- und WebView-Instanzen, die im Arbeitsspeicher gespeichert sind. Durch die Nachverfolgung dieser Werte lässt sich feststellen, ob das Arbeitsspeicherwachstum durch beibehaltene Java-Referenzen oder reine native Zuweisungen verursacht wird.

  • Private Other und Native Heap:In dumpsys meminfo werden native C/C++-Zuweisungen und benutzerdefinierte Arbeitsspeicherzuordnungen (z. B. Chromium PartitionAlloc oder eingebettete JavaScript-Laufzeit-Heaps) unter „Native Heap“ und „Private Other“ und nicht unter „Java Heap“ angezeigt.

Weitere Informationen zu Arbeitsspeicherzählern für Prozesse und ihren Kategorien finden Sie unter dem Glossar zum Arbeitsspeicher von Prozessen.

Praktische Diagnose-Workflows

Da WebView in mehreren Prozessen ausgeführt wird und nativen Arbeitsspeicher zuweist, verwenden Sie die folgenden Tools und Techniken, um die Auslastung zu prüfen:

Profiling- und Diagnosetools

Verwenden Sie die folgenden Tools, um Arbeitsspeicherzuweisungen zu prüfen und Speicherlecks zu diagnostizieren:

  • Android Studio Memory Profiler:Verwenden Sie den Speicher-Profiler, um native Zuweisungen zu visualisieren, Arbeitsspeicherkategorien im Zeitverlauf zu verfolgen und Activity Speicherlecks bei Bildschirmübergängen zu erkennen.

  • Arbeitsspeichernachverfolgung mit Perfetto: Mit Perfetto können Sie Arbeitsspeicherzähler auf Systemebene aufzeichnen (z. B. RSS und anonyme RSS), um das allgemeine Arbeitsspeicher wachstum zu beobachten. Beachten Sie, dass bei Zuweisungen der WebView nativen Engine im Heap-Profiling-Tool von Perfetto keine Callstacks erstellt werden. Verwenden Sie die Chrome-Entwicklertools, um JavaScript-Heap-Snapshots und DOM Zuweisungen in den Webinhalten zu prüfen.

Anzahl der Live-Objekte prüfen

Um festzustellen, ob das Arbeitsspeicherwachstum durch beibehaltene Java-Framework Objekte (z. B. UI-Komponenten) oder native Zuweisungen verursacht wird, prüfen Sie den Objects Abschnitt von dumpsys meminfo:

adb shell dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"

Die Ausgabe zeigt die Anzahl der Live-Objekte:

 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) mehrmals aus und vergleichen Sie die Anzahl:

  • Instanz-Speicherleck: Wenn WebViews oder Activities bei jeder Navigation zunimmt und nicht zum Ausgangswert zurückkehrt, gibt Ihre App die Java WebView-Instanz oder die Host-Activity nicht frei (z. B. aufgrund eines fehlenden ViewGroup.removeView() oder beibehaltenen Listener-Referenzen). Da eine nicht freigegebene Activity den gesamten Ansichtsbaum und die decodierten Bildressourcen im Arbeitsspeicher speichert, werden bei wiederholten Besuchen schnell der Java-Heap erschöpft und OutOfMemoryError-Abstürze verursacht.

  • Natives oder DOM-Speicherleck:Wenn WebViews und Activities konstant bleiben, während die gesamte RSS des Prozesses und Private Other weiter ansteigen, stammt das Speicherleck von nicht freigegebenen nativen Ressourcen, DOM-Elementen oder JavaScript-Engine-Bindungen. Da sich diese Zuweisungen im nativen Arbeitsspeicher befinden und den ART-Garbage Collector umgehen, sind sie für Standard-Java-Tools zur Speicherleckerkennung nicht sichtbar und sammeln sich weiter an, bis das Betriebssystem die App beendet.

Isolierten Rendererprozess über die Befehlszeile 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 Rendererprozess, in dem Webseiten gerendert werden:

  1. Suchen Sie die Prozess-ID (PID) des isolierten Rendererprozesses:

    adb shell dumpsys activity processes <var>PACKAGE_NAME</var> | grep "Isolated.*SandboxedProcessService"

    Die Ausgabe zeigt den isolierten Prozessdatensatz und seine PID RENDERER_PID (z. B. 22155):

    Isolated #5: ProcessRecord{... 22155:com.google.android.webview.debug:sandboxed_process0:...}
    
  2. Prüfen Sie die Arbeitsspeicheraufschlüsselung des Rendererprozesses anhand seiner PID:

    adb shell dumpsys meminfo <var>RENDERER_PID</var>
  3. Prüfen Sie den Host-App-Prozess, um die Auslastung auf Browserseite zu bewerten:

    adb shell dumpsys meminfo <var>PACKAGE_NAME</var>

Arbeitsspeicherzuordnungen und -zuweisungen prüfen

Wenn Sie sehen möchten, welche nativen Subsysteme oder Zuweisungen anonymen Arbeitsspeicher belegen, prüfen Sie die Arbeitsspeicherzuordnungen des Prozesses:

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 das Arbeitsspeicherwachstum aufgeführt:

Arbeitsspeicher-Tag Subsystem Relevanz für App- und Webinhalte Häufige Ursache für Arbeitsspeichererhöhung?
[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 aufzurufen, erhöht diesen Tag direkt.
[anon:scudo...] oder [anon:libc_malloc] Native Heap-Zuweisungen von 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++-Abhängigkeiten von Drittanbietern nicht freigegebene Zuweisungen bei Navigationen beibehalten.
[anon:...] (z. B. [anon:quickjs_heap...]) Benutzerdefinierte Skripting- oder native Laufzeiten Eingebettete JavaScript-Engines, benutzerdefinierte WebAssembly-Laufzeiten oder benutzerdefinierte native Pufferpools. Ja (kontextabhängig) : Häufig in Hybrid-Apps, die Skripting-Engines neben nativen Ansichten ausführen und Laufzeitbindungen nicht bereinigen.

Einschränkungen von In-App-Arbeitsspeicher-APIs

In-App-Arbeitsspeicher-APIs (z. B. Debug.getMemoryInfo oder ActivityManager.getProcessMemoryInfo) messen nur den aufrufenden Prozess. Im Modus für mehrere Prozesse können diese APIs den vom isolierten Rendererprozess verbrauchten Arbeitsspeicher nicht erfassen. Für eine genaue Bewertung des Gesamtarbeitsspeichers sollten Sie Systemtools wie dumpsys meminfo, Perfetto oder Android Studio Profiler verwenden.

Hohe Arbeitsspeichernutzung in einer Hybrid-App beheben

Wenn Sie unerklärliches Arbeitsspeicherwachstum bei wiederholten WebView-Interaktionen diagnostizieren (z. B. beim Öffnen von Weblinks oder Navigieren in webbasierten Feeds), verwenden Sie den folgenden Workflow zur Fehlerbehebung, um zu ermitteln, ob das Speicherleck in der Java-Ebene oder in der nativen Engine auftritt:

  1. Speicherlecktyp isolieren (Java im Vergleich zu 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. Öffnen und Schließen von Web artikeln oder Wischen durch Feeds).

    • Beobachtung: Wenn die Anzahl von Activities und WebViews konstant bleibt (z. B. 1–2 aktive Instanzen), gibt die App keine Activity Kontexte oder Java WebView Instanzen nicht frei.
  2. Arbeitsspeicherdelta bei Interaktionen messen (Zeitreihennachverfolgung) : Erfassen Sie dumpsys meminfo-Snapshots bei mehreren Nutzerinteraktionen, um die Zuweisungsrate pro Übergang zu berechnen:

    • Beobachtung:Der Java-Heap bleibt begrenzt und fehlerfrei (Spitzen während der Verwendung und Abfall nach der Garbage Collection), aber Private Other und Native Heap steigen bei jedem Übergang um mehrere Megabyte an. Das beweist, dass das Speicherleck vollständig im nativen Arbeitsspeicher außerhalb der ART-Laufzeit auftritt. Standardmäßige Java-Heap-Dumps (.hprof) zeigen keine Probleme.
  3. Anonyme Arbeitsspeicherzuordnungen prüfen: Prüfen 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:Das Arbeitsspeicherwachstum konzentriert sich auf [anon:partition_alloc] oder eingebettete Skripting-Engine-Heaps, begleitet von einem langsamen Anstieg der globalen JNI-Referenzen. Das bedeutet, dass zwar die Java-Ansichten ersetzt wurden, die zugrunde liegenden nativen Seitenobjekte oder JavaScript-Bindungen aber nicht freigegeben wurden.
  4. Abhilfe :

    • Achten Sie darauf, dass jedes wiederverwendete oder verworfene WebView aktive Skripts explizit beendet (stopLoading()), den Verlauf löscht und destroy() aufruft.
    • Entfernen Sie benutzerdefinierte JavaScript-Bridge-Callbacks oder globale JNI-Referenzen, die mit geschlossenen Ansichten verknüpft sind.
    • Prüfen Sie, ob sich Private Other und die RSS des Prozesses nach Navigationsübergängen stabilisieren.

Zusätzliche Ressourcen

Weitere Informationen zur Fehlerbehebung und zum Profiling von Arbeitsspeicher und WebView-Leistung finden Sie in den folgenden Ressourcen: