Bitmap-Objekte sind oft die größten einzelnen Faktoren für den Speicherbedarf einer Anwendung. Ob App-Symbole, Benachrichtigungsbilder oder Medieninhalte – eine ineffiziente Bitmap-Verarbeitung kann schnell zu Fehlern aufgrund von zu wenig Arbeitsspeicher (Out-Of-Memory, OOM) und zu einer systemweiten Speicherauslastung führen.
Bitmap-Konfigurationen und Pixeldaten
Die Menge an Arbeitsspeicher, die eine Bitmap verbraucht, wird hauptsächlich durch ihre Abmessungen (Breite × Höhe) und ihre Konfiguration (Bitmap.Config) bestimmt.
Die Konfiguration definiert, wie viele Byte zur Darstellung jedes Pixels verwendet werden:
| Konfiguration | Byte pro Pixel | Beschreibung |
|---|---|---|
ALPHA_8 |
1 | Nur Alphakanal (Transparenz). Nützlich für Masken. |
RGB_565 |
2 | Rot (5 Bit), Grün (6 Bit), Blau (5 Bit). Kein Alpha. Gut für undurchsichtige Bilder, bei denen eine hohe Farbtreue nicht entscheidend ist. |
ARGB_8888 |
4 | Alpha, Rot, Grün, Blau (jeweils 8 Bit). Standard und am häufigsten verwendet. |
RGBA_F16 |
8 | Gleitkomma mit halber Genauigkeit. Wird für Inhalte mit großem Farbraum und HDR verwendet. |
HARDWARE |
– | Im Grafikspeicher gespeichert (gralloc/DMABuf). Weitere Informationen zu Hardware-Bitmaps. |
Speicherformel: Memory (Bytes) = Width × Height × Bytes Per Pixel
Ein Vollbild auf einem 1080p-Gerät (1920 × 1080) in ARGB_8888 benötigt beispielsweise: 1920 × 1080 × 4 Byte ≈ 8, 3 MB.
Heap-Bitmaps und freigegebene Bitmaps
Heap-Bitmaps (nativer Heap)
In modernen Android-Versionen (8.0 und höher) werden Bitmap-Pixeldaten im nativen Heap, während sich nur ein kleines Wrapper-Objekt im Java-Heap befindet.
Wenn eine App ein Bild präsentieren muss, wird es normalerweise aus einer komprimierten Bilddatei in eine Bitmap decodiert und im Heap gespeichert.
Freigegebene Bitmaps (ashmem/memfd)
Wenn eine Bitmap zwischen Prozessen übertragen wird (z.B. über Binder an SystemUI für eine Benachrichtigung), vermeidet Android das Kopieren der Pixeldaten, indem gemeinsam genutzter Speicher (ashmem oder memfd) verwendet wird.
Eine Bitmap-Instanz kann explizit in den gemeinsam genutzten Speicher kopiert werden, indem
Bitmap.asShared() aufgerufen wird,
oder implizit, wenn eine Bitmap in ein Parcel eingefügt wird (in der Regel durch Hinzufügen der
Bitmap zu einem Parcelable wie einem Bundle) und über Binder IPC gesendet wird.
Wenn eine freigegebene Bitmap über Binder IPC gesendet wird, werden die Pixeldaten selbst nicht kopiert, sondern ein Dateideskriptor, der auf einen gemeinsam genutzten Speicherbereich verweist, wird für den Empfängerprozess dupliziert. Der zugrunde liegende Speicherbereich kann von mehreren Prozessen gemeinsam genutzt werden und wird erst freigegeben, wenn alle Dateideskriptoren, die darauf verweisen, geschlossen wurden.
Veränderliche und unveränderliche Bitmaps
- Veränderliche Bitmaps: Können nach der Erstellung geändert werden (z.B. über ein
Canvas). Sie erfordern immer eine eigene private Arbeitsspeicherzuweisung. Wenn eine veränderliche Bitmap kopiert wird, muss eine tiefe Kopie (zweite Kopie aller Pixeldaten) erstellt werden. - Unveränderliche Bitmaps: Können nicht geändert werden. Dies ermöglicht Optimierungen wie die gemeinsame Nutzung desselben zugrunde liegenden Speicherpuffers zwischen verschiedenen
Bitmap-Instanzen. Bitmaps, die aus APK-Ressourcen geladen wurden (BitmapFactory), sind in der Regel unveränderlich.
Effiziente Bitmap-Verarbeitung
Bitmap-Pooling und -Wiederverwendung
Das häufige Zuweisen und Freigeben von Bitmaps führt zu Zuweisungs-Churn, wodurch die Garbage Collection (GC) ständig ausgeführt wird. Gängige Bibliotheken zum Laden von Bildern verwenden einen Bitmap-Pool.
Google empfiehlt Glide als Lösung für Java-basierte Anwendungen und Coil für Kotlin-basierte Anwendungen (insbesondere bei Verwendung von Jetpack Compose).
Wenn eine Bitmap nicht mehr benötigt wird, ruft die App bitmap.recycle() auf oder gibt sie an einen Pool zurück, anstatt sie von der GC freigeben zu lassen. Wenn das nächste Mal eine Bitmap mit denselben Abmessungen und derselben Konfiguration benötigt wird, stellt der Pool den vorhandenen Puffer bereit, wodurch eine neue Zuweisung vermieden wird.
Hardware-Bitmaps
Mit Bitmap.Config.HARDWARE können Sie Pixeldaten direkt im Grafikspeicher (DMABuf) speichern.
- Vorteile:
- Speichereinsparungen: Verwendet keinen Anwendungs- oder nativen Heap, sondern GPU Speicher. Oft müssen Bitmaps, die in der UI einer App angezeigt werden, ohnehin in den GPU-Speicher kopiert werden. Dadurch wird dieser Kopiervorgang und die zusätzlichen Speicherkosten vermieden.
- Leistung: Extrem schnell zu zeichnen, da sich die Daten bereits auf der GPU befinden.
- Nachteile:
- Unveränderlich: Hardware-Bitmaps können nicht geändert werden.
- Langsames Zurücklesen: Der Zugriff auf Pixel von der CPU aus (z.B.
getPixel()) ist sehr aufwendig. - Attribution: In Standardtools wie AHAT (siehe unten) schwieriger zu verfolgen.
Praktische Übung: Bitmap-Untersuchung
Wir verwenden die Beispiel-App BitmapLab , um diese Konzepte zu untersuchen.
1. Messung mit dumpsys meminfo
Starten Sie BitmapLab und tippen Sie auf ALLOCATE 10MB ARGB_8888. Führen Sie dann diesen Befehl aus:
adb shell dumpsys meminfo -s com.android.bitmaplab
Suchen Sie in modernen Android-Versionen nach dem Abschnitt Native Allocations. Diese bieten eine viel bessere Attribution für Bitmaps als die allgemeine App Summary:
Native Allocations
Count Total(kB)
------ ------
Bitmap (malloced): 1 10240 # <--- 10MB Bitmap data!
Bitmap (nonmalloced): 0 0
- Bitmap (malloced): Bitmaps, die im nativen Heap des Prozesses zugewiesen wurden. Hier befinden sich die meisten Standard-Bitmaps in Android 8.0 und höher.
- Bitmap (nonmalloced): Bitmaps, die speziellen Speicher verwenden, z. B.
Hardware-Bitmaps oder freigegebene Bitmaps (über
ashmemodermemfd).
Wenn Sie in BitmapLab eine freigegebene Bitmap zuweisen, wird sie
in Bitmap (nonmalloced) angezeigt:
Native Allocations
Count Total(kB)
------ ------
Bitmap (malloced): 1 10240
Bitmap (nonmalloced): 1 10240 # <--- Shared Bitmap!
Tracking freigegebener Bitmaps
In einigen Android-Versionen und Kernel-Konfigurationen bietet dumpsys meminfo auch eine hochauflösende Verfolgung von Bitmaps, die über Dateideskriptoren in den Adressraum des Prozesses eingebunden werden.
Standardmäßig verwenden freigegebene Bitmaps einen generischen Namen („bitmap“). Wenn Sie eine detaillierte Attribution und eine eindeutige Bitmap-Verfolgung aktivieren möchten (um freigegebene Bitmaps in verschiedenen Prozessen zu identifizieren), müssen Sie die folgende Systemeigenschaft aktivieren:
adb shell setprop debug.hwui.bitmap_ashmem_long_name true
Wenn diese Option aktiviert ist, haben die ashmem-Regionen in /proc/<pid>/smaps aussagekräftigere Namen. meminfo nutzt dies und die Ergebnisse sehen so aus:
Shared Bitmaps
Count Size(KB)
------ ------
Mapped: 1 10240
Unique: 1 10240
- Mapped: Die Gesamtgröße aller speicherbezogenen Bitmap-Zuordnungen.
- Unique: Die Größe von Bitmaps, wobei nur eindeutige Zuordnungen berücksichtigt werden (d.h. zwei oder mehr Zuordnungen derselben zugrunde liegenden freigegebenen Bitmap-Pixeldaten werden nur einmal gezählt).
2. Bitmaps in AHAT
AHAT bietet eine hervorragende Visualisierung für Bitmaps.
- Weisen Sie in BitmapLab einige Bitmaps zu.
Erfassen Sie einen Heap-Dump mit dem Flag
-b(um native Bitmap-Daten einzubeziehen):adb shell am dumpheap -b png com.android.bitmaplab /data/local/tmp/bitmaps.hprof adb pull /data/local/tmp/bitmaps.hprof . ahat bitmaps.hprofÖffnen Sie
localhost:7100und suchen Sie in der Seitenleiste nach dem Link Bitmaps oder suchen Sie nach der KlasseBitmap.AHAT rendert die Bitmaps im Browser, sodass Sie leicht erkennen können, welche Bilder viel Arbeitsspeicher belegen.

3. Bitmap-Tracks in Perfetto
Mit Perfetto lassen sich Bitmap-Zuweisungen und -Anzahlen im Zeitverlauf verfolgen. Diese Zähler werden vom Android-Framework ausgegeben, wenn die Atrace-Kategorie gfx für eine bestimmte Anwendung aktiviert ist.
Starten Sie einen Trace. Sie müssen die Kategorie
gfxeinbeziehen und das spezifische App-Paket mit dem Flag-aangeben:external/perfetto/tools/record_android_trace -o bitmaps.perfetto-trace \ -t 15s -b 64mb view gfx dalvik am res memory -a com.android.bitmaplabTippen Sie in BitmapLab wiederholt auf die Schaltflächen Allocate und Clear.
Tippen Sie auch auf Parcel/Unparcel Bitmap.
Analysieren Sie den Trace in ui.perfetto.dev.
Im Prozessabschnitt für com.android.bitmaplab sehen Sie Folgendes:
* Bitmap Count: Ein Zähler, der die Anzahl der aktiven Bitmaps anzeigt.
* Bitmap Memory: Ein Zähler, der die von Bitmaps verwendeten Byte insgesamt anzeigt.
Slices auf hoher Ebene (Perfetto SDK)
BitmapLab verwendet auch das Perfetto SDK , um Slices auf hoher Ebene für Bitmap-Vorgänge auszugeben. Suchen Sie im Trace nach BitmapLab_. Dort finden Sie:
* BitmapLab_parcelUnparcel: Slices, die die Logik für das Parceling und Unparceling
abdecken.
* BitmapLab_postNotification: Slices, die den Ablauf zum Posten von Benachrichtigungen abdecken.
Tracking von Benachrichtigungsabläufen
Wenn Sie auf Post Notification tippen, erstellt die App eine Benachrichtigung mit der aktuellen Bitmap und sendet sie an das System. Der Framework-Code, der dafür verantwortlich ist, gibt Perfetto-Slices mit Ablaufereignissen aus, die das Parceling (Schreiben der Bitmap in ein Parcel, das über Binder IPC gesendet werden soll) und das Unparceling (Lesen der Bitmap aus einem Parcel auf der Empfängerseite) verbinden.
Im Screenshot unten sehen Sie, wie die App die große Bitmap für die Verwendung in einer Binder-Transaktion zum Posten der Benachrichtigung parselt, und das entsprechende Unparceling im Prozess system_server.

Mit Perfetto können Sie sogar dieselbe Benachrichtigungs-Bitmap verfolgen, während sie sich weiter über Threads und Prozesse hinweg ausbreitet, z. B. von einem Binder-Thread in system_server (der den Binder-Server INotificationManager implementiert) zu system_server-Worker-Threads, die dieselbe Bitmap dann an com.android.systemui weiterleiten können, damit sie im Benachrichtigungsbereich angezeigt wird.
Herausforderungen bei System-Apps
System-Apps wie SystemUI (Benachrichtigungen) und Launcher stehen vor besonderen Herausforderungen:
- Unbegrenzte Inhalte: Es kann viele Benachrichtigungen und Widgets geben. Wenn jedes eine große Bitmap enthält, kann das System schnell Out of Memory sein.
- Duplizierung: Dasselbe App-Symbol kann sich im Cache des Launchers, im Benachrichtigungsbereich von SystemUI und in der App „Einstellungen“ befinden.
- Freigabe über Hardware-Puffer: Um dies zu vermeiden, werden Systemkomponenten
auf einen zentralen Dienst zum Auslagern von Bildern umgestellt, der
HardwareBufferInstanzen prozessübergreifend freigibt. DMABuf-Attribution: Hardware-Bitmaps sparen Heap-Speicher, verwenden aber DMABuf Speicher, der in Standard Speichertools schwieriger einem bestimmten Prozess zuzuordnen ist.
Verwenden Sie
adb shell dmabuf_dump, um systemweite DMABuf-Zuweisungen zu sehen. Dieses Tool bietet eine Aufschlüsselung der Puffer nach Prozessen:droid.bitmaplab:19562 Name Rss Pss nr_procs Inode Exporter <unknown> 3840 kB 1280 kB 3 3397 virtio_gpu system 12 kB 4 kB 3 3398 system <unknown> 3840 kB 1920 kB 2 3399 virtio_gpu system 12 kB 6 kB 2 3400 system PROCESS TOTAL 11556 kB 5136 kB- RSS: Die Gesamtgröße des Puffers, wenn er im Prozess eingebunden ist.
- Pss: Die proportionale Größe (RSS geteilt durch die Anzahl der Prozesse, die den Puffer gemeinsam nutzen). Dies ist die beste Messung für die Abrechnung.
- nr_procs: Die Anzahl der Prozesse, die derzeit einen Verweis auf diesen Puffer haben.
- Exporter: Der Treiber, der den Puffer zugewiesen hat (z.B.
virtio_gpuauf Cuttlefish oder ein anbieterspezifischer Ion/DMA-BUF-Heap auf Hardware).
Sie können auch
adb shell dmabuf_dump -bverwenden, um eine Zusammenfassung aller Puffer und die gesamte systemweite DMA-BUF-Nutzung zu sehen.
← Java | ↑ Nach oben | Native →