WebView und Arbeitsspeicher

WebView ist eine leistungsstarke Komponente, mit der Sie Webinhalte in Ihrer Android-App anzeigen können. Da es sich im Wesentlichen um eine voll funktionsfähige Browser-Engine (Chromium) handelt, hat sie jedoch einen erheblichen Speicherbedarf und eine komplexe Architektur mit mehreren Prozessen.

Technischer Hintergrund: Architektur mit mehreren Prozessen

Auf modernen Android-Geräten verwendet WebView ein Modell mit mehreren Prozessen, um die Sicherheit und Stabilität zu verbessern. Wenn Ihre App eine WebView verwendet, wird der Arbeitsspeicher auf verschiedene Prozesse verteilt:

  1. Browserprozess (App-Prozess): Dies ist der Haupt prozess Ihrer Anwendung. Er enthält das Java-Objekt WebView und den „Browser“-Teil der Chromium-Engine. Dieser Prozess verwaltet die UI, Netzwerkanfragen und das GPU-Rendering (direkt in die Android HWUI-Renderingpipeline integriert). Im Gegensatz zu Chrome hat WebView keinen separaten GPU-Prozess.
  2. Renderer-Prozess: Dieser Prozess ist für das Parsen von HTML, die Ausführung von JavaScript und das Layout verantwortlich. Aus Sicherheitsgründen ist er vom Rest des Systems isoliert. Derzeit erhalten Apps nur einen Renderer-Prozess für alle WebViews (mit Ausnahme einiger seltener Sonderfälle). Chrome verwendet oft separate Renderer-Prozesse für verschiedene Websites.

WebView-Architektur

Warum das für den Arbeitsspeicher wichtig ist

Wenn Sie dumpsys meminfo <your_package> verwenden, sehen Sie nur den Arbeitsspeicher, der von dem Browserprozess (App-Prozess) verwendet wird. Der vom Renderer-Prozess verwendete Arbeitsspeicher wird separat berücksichtigt.

Im Browserprozess wird der WebView-Arbeitsspeicher so verteilt:

  • Java-Heap: Enthält den WebView Java-Wrapper und zugehörige Objekte.
  • Nativer Heap: Enthält die internen Daten strukturen, Caches und den Status der Chromium-Browser-Engine. Aufgrund der Verwendung von PartitionAlloc werden einige native WebView-Zuweisungen in dumpsys meminfo möglicherweise nicht unter „Native Heap“ gezählt, sondern stattdessen unter „Other“ oder „Unknown“ aufgeführt.
  • Gemeinsamer Arbeitsspeicher: Wird für die gemeinsame Nutzung von Grafikpuffern und anderen Daten verwendet. Dieser wird von dumpsys meminfo möglicherweise nicht eindeutig kategorisiert.

Tools zur Fehlerbehebung

Chrome-Entwicklertools

Das leistungsstärkste Tool zum Analysieren des Arbeitsspeichers in der WebView (Renderer-Prozess) sind die Chrome-Entwicklertools.

  1. So aktivieren Sie das WebView-Debugging in Ihrer App:

    // NOTE: In production, this should be gated behind a developer setting
    // or only enabled for debuggable builds to prevent reverse engineering.
    WebView.setWebContentsDebuggingEnabled(true);
    
  2. Verbinden Sie Ihr Gerät über USB.

  3. Öffnen Sie Chrome auf Ihrem Hostcomputer und rufen Sie chrome://inspect/#devices auf.

  4. Suchen Sie Ihre App und klicken Sie auf Untersuchen.

  5. Wechseln Sie im Fenster der Entwicklertools zum Tab Arbeitsspeicher, um Heap-Snapshots zu erstellen oder Zuweisungszeitachsen für den JavaScript-Heap aufzuzeichnen.

dumpsys meminfo

Verwenden Sie adb shell dumpsys meminfo --all <package>, um eine Aufschlüsselung des Arbeitsspeichers zu sehen. Suchen Sie in der Ausgabe nach der Kategorie WebView und der Anzahl der Objekte.

Renderer-Profil erstellen

Da der Renderer in einem separaten Prozess ausgeführt wird, können Sie seinen nativen Heap nicht einfach durch die Profilerstellung Ihrer App analysieren. Sie müssen die PID des Renderer-Prozesses speziell identifizieren.

So identifizieren Sie die richtige Renderer-PID, wenn mehrere WebViews aktiv sind:

  1. Verwenden Sie dumpsys activity:

    adb shell dumpsys activity processes <your_package_name>
    

    Suchen Sie nach dem Abschnitt mConnections. Sie sehen einen ConnectionRecord, der Ihre App mit einem SandboxedProcessService verknüpft. Die PID dieses Prozesses ist Ihr Renderer. Beispiel:

    mConnections:
      - ConnectionRecord{... com.android.memorylab/org.chromium.content.app.SandboxedProcessService0:0 ...}
    
  2. Prozessnamen prüfen: Renderer-Prozesse haben in der Regel den Namen com.google.android.webview:sandboxed_processX oder ähnlich. Wenn nur eine App eine WebView verwendet, gibt es wahrscheinlich nur eine.

Sobald Sie die PID haben, können Sie sie mit heapprofd analysieren.

Best Practices für den WebView-Arbeitsspeicher

Explizite Zerstörung

Apps müssen WebView.destroy() aufrufen, um anzugeben, wann sie mit einer Instanz fertig sind.

WebView versucht zwar, dafür zu sorgen, dass Instanzen automatisch per Garbage Collection erfasst und alle Ressourcen freigegeben werden, aber das ist nicht in allen Fällen garantiert. Auch wenn die automatische Garbage Collection funktioniert, kann sie sich erheblich verzögern, sodass die App Ressourcen viel länger als erwartet belegt.

Wenn eine App WebView.destroy() zum richtigen Zeitpunkt aufruft (z.B. in Activity.onDestroy()), werden durch das Beibehalten eines Verweises auf das WebView Objekt selbst keine nennenswerten nativen Ressourcen verloren. Es ist nicht unbedingt erforderlich, Verweise auf das WebView-Objekt in Aktivitätsfeldern nach der Zerstörung auf null zu setzen, da es bereinigt wird, wenn die Aktivität selbst per Garbage Collection erfasst wird.

Übungen: Praktische Arbeit mit dem WebView-Arbeitsspeicher

Übung 1: Arbeitsspeicherbedarf bei mehreren Prozessen beobachten

  1. Starten Sie MemoryLab und nehmen Sie eine Baseline-Messung des Arbeitsspeichers Ihrer App vor:

    adb shell dumpsys meminfo com.android.memorylab
    

    Beispiel-Baseline (rango) : TOTAL PSS: 18915 KB

  2. Tippen Sie auf WebView starten (normal).

  3. Tippen Sie in der WebView mehrmals auf JS-Arbeitsspeicher zuweisen (1000 DIVs).

  4. Prüfen Sie noch einmal den Arbeitsspeicher der App:

    adb shell dumpsys meminfo com.android.memorylab
    
  5. Der Arbeitsspeicher in Ihrem App-Prozess steigt im Vergleich zur Baseline nicht wesentlich an. Das liegt daran, dass sich die DOM-Elemente im Renderer-Prozess befinden.

  6. Suchen Sie den Renderer-Prozess:

    adb shell ps -A | grep webview | grep sandboxed
    

    Beispielausgabe:

    u0_i9002     14227  1087    1632732 135880 do_epoll_wait       0 S com.google.android.webview:sandboxed_process0
    
  7. Prüfen Sie den Arbeitsspeicher des Renderer-Prozesses (anhand seiner PID):

    adb shell dumpsys meminfo 14227
    
  8. Beachten Sie die hohe TOTAL PSS des Renderer-Prozesses. In unserem Beispiel ist sie nach einigen Zuweisungen auf ~55 MB gestiegen. JavaScript-Zuweisungen (die von der V8-Engine verarbeitet werden) tragen in der Regel zu den Abschnitten Private Other oder Unknown (mmap) von dumpsys meminfo bei und nicht zum Dalvik-Heap.

Übung 2: WebView-Speicherleck auf der Java-Seite

Ein häufiger Fehler ist, eine WebView-Instanz in einem statischen Feld oder in einem langlebigen Objekt zu behalten, das ein Speicherleck verursacht. Da das WebView-Objekt ein schwerer „Anker“ ist, der native Ressourcen und möglicherweise ganze Renderer-Prozesse belegt, ist ein Speicherleck sehr kostspielig.

Auswirkungen von WebView-Leaks

  1. Tippen Sie in MemoryLab auf WebView starten (Java-Speicherleck).
  2. Die Aktivität wird automatisch geschlossen, nachdem die Seite geladen wurde (simuliert wiederholte Navigation und Ansammlung von Speicherlecks).
  3. Tippen Sie viermal auf die Schaltfläche.
  4. Prüfen Sie die Anzahl der WebView-Instanzen in Ihrer App:

    adb shell dumpsys meminfo com.android.memorylab
    

    Suchen Sie unten nach dem Abschnitt Objects. Die Anzahl der WebViews ist auf 4 gestiegen.

    Beispielausgabe (4 Speicherlecks) auf rango:

     Objects
               Views:       51         ViewRootImpl:        5
         AppContexts:       14           Activities:        5
              Assets:       38        AssetManagers:        0
       Local Binders:       55        Proxy Binders:       77
       Parcel memory:       41         Parcel count:       68
    Death Recipients:        3             WebViews:        4
    
  5. Erfassen Sie einen Heap-Dump und verwenden Sie AHAT , um das Speicherleck zu finden. Wenn ahat nicht in Ihrem Pfad vorhanden ist, können Sie es aus dem Android-Baum erstellen:

    # Dump heap from device
    adb shell am dumpheap com.android.memorylab /data/local/tmp/heap.hprof
    adb pull /data/local/tmp/heap.hprof
    # Run ahat using the built JAR (found in out/host/linux-x86/framework/)
    java -jar out/host/linux-x86/framework/ahat.jar -p 8888 heap.hprof
    
  6. Klicken Sie in der AHAT-Weboberfläche (localhost:8888) im oberen Menü auf den Link allocations (oder sites), um die Arbeitsspeichernutzung zu sehen.

    AHAT-Zuweisungen

  7. Suchen Sie nach der Klasse android.webkit.WebView. Klicken Sie auf die Anzahl der Instanzen , um alle aktiven Instanzen zu sehen. In der Liste sollten mehrere Instanzen zu sehen sein.

    AHAT WebView-Instanzen

  8. Klicken Sie auf eine der WebView-Instanzen mit Speicherleck. Scrollen Sie nach unten zum Abschnitt Sample Path from GC Root. Sie sehen, dass sie von der Liste sLeakedWebViews in com.android.memorylab.WebViewActivity belegt wird.

    AHAT-Pfad zum GC-Root


← Nativ | ↑ Nach oben | App-Code →