Les objets bitmap sont souvent les principaux contributeurs à l'espace mémoire utilisé d'une application. Qu'il s'agisse d'icônes d'application, d'images de notification ou de contenu multimédia, une gestion inefficace des bitmaps peut rapidement entraîner des erreurs de mémoire insuffisante et une pression sur la mémoire à l'échelle du système.
Configurations bitmap et données de pixels
La quantité de mémoire consommée par un bitmap est principalement déterminée par ses dimensions (largeur × hauteur) et sa configuration (Bitmap.Config).
La configuration définit le nombre d'octets utilisés pour représenter chaque pixel :
| Configuration | Octets par pixel | Description |
|---|---|---|
ALPHA_8 |
1 | Canal alpha (transparence) uniquement. Utile pour les masques. |
RGB_565 |
2 | Rouge (5 bits), vert (6 bits), bleu (5 bits). Pas d'alpha. Convient aux images opaques où la fidélité des couleurs n'est pas essentielle. |
ARGB_8888 |
4 | Alpha, rouge, vert, bleu (8 bits chacun). Par défaut et le plus courant. |
RGBA_F16 |
8 | Virgule flottante semi-précision. Utilisé pour le contenu à large gamme de couleurs et HDR. |
HARDWARE |
N/A | Stocké dans la mémoire graphique (gralloc/DMABuf). Voir Bitmaps matériels. |
Formule de mémoire : Memory (Bytes) = Width × Height × Bytes Per Pixel
Par exemple, une image plein écran sur un appareil 1080p (1920 x 1080) en ARGB_8888 prend : 1920 × 1080 × 4 octets ≈ 8,3 Mo.
Bitmaps de segment de mémoire et bitmaps partagés
Bitmaps de segment de mémoire (segment de mémoire natif)
Dans les versions modernes d'Android (8.0 ou ultérieures), les données de pixels bitmap sont stockées dans le segment de mémoire natif, tandis qu'un petit objet wrapper réside dans le segment de mémoire Java.
Lorsqu'une application doit présenter une image, elle est généralement décodée à partir d'un fichier image compressé dans un bitmap et stockée dans le tas de mémoire.
Bitmaps partagés (ashmem/memfd)
Lorsqu'un bitmap est transféré entre des processus (par exemple, via Binder vers SystemUI pour une notification), Android évite de copier les données de pixels en utilisant la mémoire partagée (ashmem ou memfd).
Une instance Bitmap peut être copiée explicitement dans la mémoire partagée en appelant
Bitmap.asShared(),
ou implicitement si un Bitmap est placé dans un Parcel (généralement en ajoutant le
bitmap à un Parcelable tel qu'un Bundle) et envoyé via Binder IPC.
Lorsqu'un bitmap partagé est envoyé via Binder IPC, les données de pixels elles-mêmes ne sont pas copiées, mais un descripteur de fichier référençant une région de mémoire partagée est dupliqué dans le processus destinataire. La région de mémoire sous-jacente peut être partagée entre plusieurs processus et n'est pas libérée tant que tous les descripteurs de fichiers qui y font référence n'ont pas été fermés.
Bitmaps modifiables et immuables
- Bitmaps modifiables : peuvent être modifiés après leur création (par exemple, via un
Canvas). Ils nécessitent toujours leur propre allocation de mémoire privée. Si un bitmap modifiable est copié, une copie complète (deuxième copie de toutes les données de pixels) doit être effectuée. - Bitmaps immuables : ne peuvent pas être modifiés. Cela permet des optimisations telles que le partage du même tampon de mémoire sous-jacent entre différentes instances
Bitmap. Les bitmaps chargés à partir de ressources APK (BitmapFactory) sont généralement immuables.
Gestion efficace des bitmaps
Mise en pool et réutilisation des bitmaps
L'allocation et la désallocation fréquentes de bitmaps entraînent une rotation de l'allocation, ce qui force le GC à s'exécuter en permanence. Les bibliothèques de chargement d'images courantes utilisent un pool de bitmaps.
Google recommande Glide comme solution pour les applications basées sur Java et Coil pour les applications basées sur Kotlin (en particulier lors de l'utilisation de Jetpack Compose).
Lorsqu'un bitmap n'est plus nécessaire, au lieu de le laisser être récupéré par le GC, l'application appelle bitmap.recycle() ou le renvoie à un pool. La prochaine fois qu'un bitmap des mêmes dimensions et de la même configuration sera nécessaire, le pool fournira le tampon existant, ce qui évitera une nouvelle allocation.
Bitmaps matériels
Bitmap.Config.HARDWARE vous permet de stocker directement les données de pixels dans la mémoire graphique (DMABuf).
- Avantages:
- Économies de mémoire : n'utilise pas le segment de mémoire d'application ni le segment de mémoire natif, mais la mémoire GPU. Souvent, les bitmaps affichés dans l'interface utilisateur d'une application doivent de toute façon être copiés dans la mémoire GPU. Cela permet donc d'économiser cette opération de copie et le coût de mémoire supplémentaire.
- Performances : le dessin est extrêmement rapide, car les données se trouvent déjà sur le GPU.
- Inconvénients:
- Immuable : les bitmaps matériels ne peuvent pas être modifiés.
- La lecture est lente : l'accès aux pixels depuis le processeur (par exemple,
getPixel()) est très coûteux. - Attribution : plus difficile à suivre dans les outils standards tels qu'AHAT (voir ci-dessous).
Exercice pratique : exploration des bitmaps
Nous allons utiliser l'exemple d'application BitmapLab pour explorer ces concepts.
1. Mesure avec dumpsys meminfo
Lancez BitmapLab , puis appuyez sur ALLOCATE 10MB ARGB_8888. Exécutez ensuite la commande ci-dessous :
adb shell dumpsys meminfo -s com.android.bitmaplab
Dans les versions modernes d'Android, recherchez la section Native Allocations (Allocations natives). Elles offrent une bien meilleure attribution pour les bitmaps que le App Summary (Résumé de l'application) générique :
Native Allocations
Count Total(kB)
------ ------
Bitmap (malloced): 1 10240 # <--- 10MB Bitmap data!
Bitmap (nonmalloced): 0 0
- Bitmap (malloced) : bitmaps alloués dans le segment de mémoire natif du processus. C'est là que la plupart des bitmaps standards résident dans Android 8.0 ou version ultérieure.
- Bitmap (nonmalloced) : bitmaps qui utilisent une mémoire spécialisée telle que les
bitmaps matériels ou les bitmaps partagés (via
ashmemoumemfd).
Si vous allouez un bitmap partagé dans BitmapLab, il s'affiche
dans Bitmap (nonmalloced) :
Native Allocations
Count Total(kB)
------ ------
Bitmap (malloced): 1 10240
Bitmap (nonmalloced): 1 10240 # <--- Shared Bitmap!
Suivi des bitmaps partagés
Sur certaines versions d'Android et configurations de noyau, dumpsys meminfo fournit également un suivi haute résolution des bitmaps mappés dans l'espace d'adressage du processus via des descripteurs de fichiers.
Par défaut, les bitmaps partagés utilisent un nom générique ("bitmap"). Pour activer l'attribution détaillée et le suivi unique des bitmaps (identification des bitmaps partagés dans différents processus), vous devez activer la propriété système suivante :
adb shell setprop debug.hwui.bitmap_ashmem_long_name true
Une fois activées, les régions ashmem de /proc/<pid>/smaps auront des noms plus
descriptifs. meminfo en tirera parti, et les résultats se présenteront comme suit :
Shared Bitmaps
Count Size(KB)
------ ------
Mapped: 1 10240
Unique: 1 10240
- Mapped (Mappé) : taille totale de tous les mappages de mémoire liés aux bitmaps.
- Unique (Unique) : taille des bitmaps en ne considérant que les éléments uniques (c'est-à-dire que deux mappages ou plus des mêmes données de pixels bitmap partagées sous-jacentes ne sont comptabilisés qu'une seule fois).
2. Bitmaps dans AHAT
AHAT offre une excellente visualisation des bitmaps.
- Dans BitmapLab, allouez quelques bitmaps.
Capturez une empreinte de la mémoire avec l'option
-b(pour inclure les données bitmap natives) :adb shell am dumpheap -b png com.android.bitmaplab /data/local/tmp/bitmaps.hprof adb pull /data/local/tmp/bitmaps.hprof . ahat bitmaps.hprofOuvrez
localhost:7100et recherchez le lien Bitmaps dans la barre latérale ou la classeBitmap.AHAT affichera les bitmaps dans le navigateur, ce qui vous permettra d'identifier facilement les images qui consomment de la mémoire.

3. Pistes bitmap dans Perfetto
Perfetto peut suivre les allocations et les nombres de bitmaps au fil du temps. Ces compteurs sont émis par le framework Android lorsque la catégorie atrace gfx est activée pour une application spécifique.
Démarrez une trace. Vous devez inclure la catégorie
gfxet cibler le package d'application spécifique à l'aide de l'option-a:external/perfetto/tools/record_android_trace -o bitmaps.perfetto-trace \ -t 15s -b 64mb view gfx dalvik am res memory -a com.android.bitmaplabDans BitmapLab, appuyez plusieurs fois sur les boutons Allocate (Allouer) et Clear (Effacer).
Appuyez également sur Parcel/Unparcel Bitmap (Bitmap de colis/déballage).
Analysez la trace dans ui.perfetto.dev.
Dans la section de processus pour com.android.bitmaplab, vous verrez :
* Nombre de bitmaps : compteur indiquant le nombre de bitmaps actifs.
* Bitmap Memory (Mémoire bitmap) : compteur indiquant le nombre total d'octets utilisés par les bitmaps.
Tranches de haut niveau (SDK Perfetto)
BitmapLab utilise également le SDK Perfetto pour émettre des tranches de haut niveau pour les opérations bitmap. Recherchez BitmapLab_ dans la trace pour trouver :
* BitmapLab_parcelUnparcel : tranches couvrant la logique de colisage et de déballage.
* BitmapLab_postNotification : tranches couvrant le flux de publication des notifications.
Suivi des flux de notification
Lorsque vous appuyez sur Post Notification (Publier une notification), l'application crée une notification contenant le bitmap actuel et l'envoie au système. Le code du framework responsable de cette opération émet des tranches Perfetto avec des événements de flux qui connectent le colisage (écriture du bitmap dans un colis à envoyer via Binder IPC) et le déballage (lecture du bitmap à partir d'un colis à l'extrémité de réception).
Dans la capture d'écran ci-dessous, vous pouvez voir l'application qui emballe le grand bitmap à utiliser dans une transaction Binder pour publier la notification, ainsi que le déballage correspondant dans le processus system_server.

Avec Perfetto, vous pouvez même suivre le même bitmap de notification lorsqu'il se propage davantage entre les threads et les processus, par exemple d'un thread Binder dans system_server (qui implémente le serveur Binder INotificationManager) vers les threads de travail system_server qui peuvent ensuite transférer le même bitmap vers com.android.systemui pour qu'il s'affiche dans le volet des notifications.
Défis liés aux applications système
Les applications système telles que SystemUI (Notifications) et Launcher sont confrontées à des défis uniques :
- Contenu illimité : les notifications et les widgets peuvent être nombreux. Si chacun contient un grand bitmap, le système peut rapidement manquer de mémoire.
- Duplication : la même icône d'application peut être conservée dans le cache du lanceur, la zone de notification de SystemUI et l'application Paramètres.
- Partage via des tampons matériels : pour atténuer ce problème, les composants système
évoluent vers un service centralisé de "déchargement d'images" qui partage les instances
HardwareBufferentre les processus. Attribution DMABuf : les bitmaps matériels économisent de l'espace de segment de mémoire, mais utilisent la mémoire DMABuf , qui est plus difficile à attribuer à un processus spécifique dans les outils de mémoire standards.
Utilisez
adb shell dmabuf_dumppour afficher les allocations DMABuf à l'échelle du système. Cet outil fournit une répartition des tampons par processus :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 : taille totale du tampon s'il est mappé dans le processus.
- Pss : taille proportionnelle (RSS divisé par le nombre de processus partageant le tampon). Il s'agit de la meilleure métrique pour la comptabilité.
- nr_procs : nombre de processus qui détiennent actuellement une référence à ce tampon.
- Exporter : pilote qui a alloué le tampon (par exemple,
virtio_gpusur Cuttlefish ou un segment de mémoire Ion/DMA-BUF spécifique au fournisseur sur le matériel).
Vous pouvez également utiliser
adb shell dmabuf_dump -bpour obtenir un résumé de tous les tampons et de l'utilisation totale du DMA-BUF à l'échelle du système.