Os objetos de bitmap são, com frequência, os maiores contribuintes individuais para o consumo de memória de um aplicativo. Sejam ícones de apps, imagens de notificação ou conteúdo de mídia, o processamento ineficiente de bitmaps pode levar rapidamente a erros de falta de memória (OOM, na sigla em inglês) e pressão de memória em todo o sistema.
Configurações de bitmap e dados de pixel
A quantidade de memória consumida por um bitmap é determinada principalmente pelas dimensões (largura × altura) e pela configuração (Bitmap.Config).
A configuração define quantos bytes são usados para representar cada pixel:
| Configuração | Bytes por pixel | Descrição |
|---|---|---|
ALPHA_8 |
1 | Somente canal alfa (transparência). Útil para máscaras. |
RGB_565 |
2 | Vermelho (5 bits), verde (6 bits), azul (5 bits). Sem alfa. Bom para imagens opacas em que a alta fidelidade de cores não é essencial. |
ARGB_8888 |
4 | Alfa, vermelho, verde, azul (8 bits cada). Padrão e mais comum. |
RGBA_F16 |
8 | Ponto flutuante de meia precisão. Usado para conteúdo HDR e de gama ampla. |
HARDWARE |
N/A | Armazenado na memória gráfica (gralloc/DMABuf). Consulte Bitmaps de hardware. |
Fórmula de memória:Memory (Bytes) = Width × Height × Bytes Per Pixel
Por exemplo, uma imagem de tela cheia em um dispositivo de 1080p (1920 x 1080) em ARGB_8888 ocupa: 1920 × 1080 × 4 bytes ≈ 8,3 MB.
Bitmaps de heap x bitmaps compartilhados
Bitmaps de heap (heap nativo)
No Android moderno (8.0 e mais recentes), os dados de pixel do bitmap são armazenados no heap nativo, enquanto apenas um pequeno objeto wrapper reside no heap Java.
Quando um app precisa apresentar uma imagem, ela geralmente é decodificada de um arquivo de imagem compactado em um bitmap e armazenada no heap.
Bitmaps compartilhados (ashmem/memfd)
Quando um bitmap é transferido entre processos (por exemplo, via Binder para SystemUI para uma notificação), o Android evita copiar os dados de pixel usando memória compartilhada (ashmem ou memfd).
Uma instância Bitmap pode ser copiada para a memória compartilhada explicitamente chamando
Bitmap.asShared(),
ou implicitamente se um Bitmap for colocado dentro de um Parcel (normalmente adicionando o
Bitmap a um Parcelable, como um Bundle) e enviado pelo Binder IPC.
Quando um bitmap compartilhado é enviado pelo Binder IPC, os dados de pixel em si não são copiados, mas um descritor do arquivo que faz referência a uma região de memória compartilhada é duplicado para o processo do destinatário. A região de memória subjacente pode ser compartilhada entre vários processos e não é liberada até que todos os descritores de arquivo que fazem referência a ela sejam fechados.
Bitmaps mutáveis x imutáveis
- Bitmaps mutáveis: podem ser modificados após a criação (por exemplo, usando um
Canvas). Eles sempre exigem a própria alocação de memória privada. Se um bitmap mutável for copiado, uma cópia completa (segunda cópia de todos os dados de pixel) precisará ser feita. - Bitmaps imutáveis: não podem ser alterados. Isso permite otimizações, como compartilhar o mesmo buffer de memória subjacente entre diferentes instâncias
Bitmap. Os bitmaps carregados de recursos de APK (BitmapFactory) geralmente são imutáveis.
Processamento eficiente de bitmaps
Pool e reutilização de bitmaps
A alocação e desalocação frequentes de bitmaps causam churn de alocação, o que força o GC a ser executado constantemente. As bibliotecas comuns de carregamento de imagens usam um pool de bitmaps.
O Google recomenda o Glide como solução para aplicativos baseados em Java e o Coil para aplicativos baseados em Kotlin (especialmente ao usar o Jetpack Compose).
Quando um bitmap não é mais necessário, em vez de deixá-lo ser coletado pelo GC, o app chama bitmap.recycle() ou o retorna a um pool. Na próxima vez que um bitmap das mesmas dimensões e configuração for necessário, o pool vai fornecer o buffer existente, evitando uma nova alocação.
Bitmaps de hardware
Bitmap.Config.HARDWARE permite armazenar dados de pixel diretamente na memória gráfica (DMABuf).
- Prós:
- Economia de memória: não usa o heap nativo ou do aplicativo; usa a memória da GPU. Muitas vezes, os bitmaps mostrados na interface de um app precisam ser copiados para a memória da GPU. Isso economiza a operação de cópia e o custo de memória adicional.
- Performance: extremamente rápido de desenhar porque os dados já estão na GPU.
- Contras:
- Imutável: os bitmaps de hardware não podem ser modificados.
- A leitura é lenta: o acesso a pixels da CPU (por exemplo,
getPixel()) é muito caro. - Atribuição: mais difícil de rastrear em ferramentas padrão, como o AHAT (consulte abaixo).
Exercício prático: exploração de bitmap
Vamos usar o app de exemplo BitmapLab para explorar esses conceitos.
1. Medição com dumpsys meminfo
Inicie o BitmapLab e toque em ALLOCATE 10MB ARGB_8888. Depois execute:
adb shell dumpsys meminfo -s com.android.bitmaplab
Em versões modernas do Android, procure a seção Native Allocations. Elas oferecem uma atribuição muito melhor para bitmaps do que o App Summary genérico:
Native Allocations
Count Total(kB)
------ ------
Bitmap (malloced): 1 10240 # <--- 10MB Bitmap data!
Bitmap (nonmalloced): 0 0
- Bitmap (malloced): bitmaps alocados no heap nativo do processo. É aqui que a maioria dos bitmaps padrão reside no Android 8.0 e mais recentes.
- Bitmap (nonmalloced): bitmaps que usam memória especializada, como
bitmaps de hardware ou bitmaps compartilhados (via
ashmemoumemfd).
Se você alocar um bitmap compartilhado no BitmapLab, ele vai aparecer
em Bitmap (nonmalloced):
Native Allocations
Count Total(kB)
------ ------
Bitmap (malloced): 1 10240
Bitmap (nonmalloced): 1 10240 # <--- Shared Bitmap!
Rastreamento de bitmaps compartilhados
Em algumas versões do Android e configurações de kernel, dumpsys meminfo também oferece rastreamento de alta resolução para bitmaps mapeados no espaço de endereço do processo usando descritores de arquivo.
Por padrão, os bitmaps compartilhados usam um nome genérico ("bitmap"). Para ativar a atribuição detalhada e o rastreamento de bitmap exclusivo (identificando bitmaps compartilhados em diferentes processos), ative a seguinte propriedade do sistema:
adb shell setprop debug.hwui.bitmap_ashmem_long_name true
Quando ativadas, as regiões ashmem em /proc/<pid>/smaps terão nomes mais
descritivos. meminfo vai aproveitar isso, e os resultados serão assim:
Shared Bitmaps
Count Size(KB)
------ ------
Mapped: 1 10240
Unique: 1 10240
- Mapeado: o tamanho total de todos os mapeamentos de memória relacionados a bitmap.
- Exclusivo: o tamanho dos bitmaps considerando apenas os exclusivos (ou seja, dois ou mais mapeamentos dos mesmos dados de pixel de bitmap compartilhado subjacente contam apenas uma vez).
2. Bitmaps no AHAT
O AHAT oferece uma excelente visualização para bitmaps.
- No BitmapLab, aloque alguns bitmaps.
Capture um heap dump com a flag
-b(para incluir dados de bitmap nativo):adb shell am dumpheap -b png com.android.bitmaplab /data/local/tmp/bitmaps.hprof adb pull /data/local/tmp/bitmaps.hprof . ahat bitmaps.hprofAbra
localhost:7100e procure o link Bitmaps na barra lateral ou pesquise a classeBitmap.O AHAT vai renderizar os bitmaps no navegador, facilitando a identificação de quais imagens estão consumindo memória.

3. Faixas de bitmap no Perfetto
O Perfetto pode rastrear alocações e contagens de bitmap ao longo do tempo. Esses contadores são emitidos pelo framework do Android quando a categoria gfx atrace está ativada para um aplicativo específico.
Inicie um rastreamento. Você precisa incluir a categoria
gfxe segmentar o pacote do app específico usando a flag-a:external/perfetto/tools/record_android_trace -o bitmaps.perfetto-trace \ -t 15s -b 64mb view gfx dalvik am res memory -a com.android.bitmaplabNo BitmapLab, toque nos botões Allocate e Clear repetidamente.
Toque também em Parcel/Unparcel Bitmap.
Analise o rastreamento em ui.perfetto.dev.
Na seção de processos para com.android.bitmaplab, você verá:
* Bitmap Count: um contador que mostra o número de bitmaps ativos.
* Memória de Bitmap: um contador que mostra o total de bytes usados por bitmaps.
Fatias de alto nível (SDK do Perfetto)
O BitmapLab também usa o SDK do Perfetto para emitir fatias de alto nível para operações de bitmap. Pesquise BitmapLab_ no rastreamento para encontrar:
* BitmapLab_parcelUnparcel: fatias que abrangem a lógica de empacotamento e desempacotamento
logic.
* BitmapLab_postNotification: fatias que abrangem o fluxo de postagem de notificações.
Como rastrear fluxos de notificação
Quando você toca em Post Notification, o app cria uma notificação contendo o bitmap atual e a envia para o sistema. O código do framework responsável por isso emite fatias do Perfetto com eventos de fluxo que conectam o empacotamento (gravação do bitmap em um pacote a ser enviado pelo Binder IPC) e o desempacotamento (leitura do bitmap de um pacote na extremidade de recebimento).
Na captura de tela abaixo, é possível ver o app empacotando o bitmap grande a ser usado em uma transação do Binder para postar a notificação e o desempacotamento correspondente no processo system_server.

Usando o Perfetto, você pode até mesmo seguir o mesmo bitmap de notificação à medida que ele se propaga ainda mais em threads e processos, por exemplo, de uma thread do Binder em system_server (que implementa o servidor do Binder INotificationManager) para threads de trabalho system_server que podem encaminhar o mesmo bitmap para com.android.systemui para ser mostrado na sombra de notificações.
Desafios de apps do sistema
Apps do sistema, como SystemUI (notificações) e Launcher , enfrentam desafios exclusivos:
- Conteúdo ilimitado: as notificações e os widgets podem ser numerosos. Se cada um deles tiver um bitmap grande, o sistema poderá ficar sem memória rapidamente.
- Duplicação: o mesmo ícone do app pode ser mantido no cache do Launcher, na área de notificação do SystemUI e no app Configurações.
- Compartilhamento via buffers de hardware: para atenuar isso, os componentes do sistema estão
migrando para um serviço centralizado de "descarregamento de imagens" que compartilha
HardwareBufferinstâncias entre processos. Atribuição de DMABuf: os bitmaps de hardware economizam espaço de heap, mas usam a memória DMABuf , que é mais difícil de atribuir a um processo específico em ferramentas de memória padrão.
Use
adb shell dmabuf_dumppara conferir as alocações de DMABuf em todo o sistema. Essa ferramenta fornece uma detalhamento de buffers por processo: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: o tamanho total do buffer se ele estiver mapeado no processo.
- Pss: o tamanho proporcional (RSS dividido pelo número de processos que compartilham o buffer). Essa é a melhor métrica para contabilidade.
- nr_procs: o número de processos que atualmente têm uma referência a esse buffer.
- Exportador: O driver que alocou o buffer (por exemplo,
virtio_gpuno Cuttlefish ou um heap Ion/DMA-BUF específico do fornecedor no hardware).
Também é possível usar
adb shell dmabuf_dump -bpara um resumo de todos os buffers e o uso total de DMA-BUF em todo o sistema.