Otimizar imagens bitmap

Trabalhar com imagens pode introduzir problemas de performance rapidamente se você não tomar cuidado. Mesmo um pequeno gráfico em um formato compactado, como JPG ou PNG, pode se transformar em um bitmap grande quando é decodificado para exibição. Se você não for eficiente ao usar gráficos, poderá ter problemas de memória que prejudicam o desempenho do seu app e de outros apps no dispositivo. Siga estas práticas recomendadas para garantir que o app tenha a melhor performance.

Usar bibliotecas de carregamento de imagens

Você pode melhorar a eficiência do app usando bibliotecas de carregamento de imagens como Coil (para projetos Kotlin) ou Glide (para projetos Java). Essas bibliotecas reduzem o uso de memória do app fazendo coisas como armazenar imagens em cache, reduzir a resolução de gráficos quando necessário e reciclar objetos gráficos.

Reduzir a resolução das imagens

Use o tamanho da imagem adequado às suas necessidades. Evite carregar uma imagem grande e de alta resolução em um contêiner pequeno, como uma miniatura. Em vez disso, use a redução de resolução para diminuir a escala da imagem antes de decodificá-la na memória.

Subamostragem do lado do cliente

Bibliotecas de carregamento de imagens, como Coil e Glide, processam a subamostragem automaticamente. É possível configurar as estratégias de subamostragem usando ImageLoader (para Coil) ou DownsampleStrategy (para Glide). Se você estiver gerenciando bitmaps manualmente, use inSampleSize para decodificar uma versão menor. Para fazer isso com segurança, primeiro defina inJustDecodeBounds como true para ler as dimensões da imagem sem alocar memória, calcule o tamanho da amostra, defina inSampleSize como esse valor, defina inJustDecodeBounds como false e decodifique a imagem.

Prefira o redimensionamento do lado do servidor

Sempre que possível, solicite as dimensões exatas da imagem necessárias diretamente do servidor de back-end. Isso reduz o uso da rede e a ocupação do cache de disco, além de proporcionar um uso mais leve da memória, evitando o overhead de memória do redimensionamento de imagens no dispositivo.

É possível configurar bibliotecas para anexar dinamicamente o tamanho da visualização de destino ao URL da imagem. Por exemplo, o Coil permite isso usando interceptadores personalizados, e o Glide é compatível com isso usando carregadores de modelos personalizados (como BaseGlideUrlLoader).

Evitar tamanhos de layout sem restrições

Para que os carregadores de imagens façam o downsampling (lado do cliente ou do lado do servidor) de maneira eficaz, eles precisam saber o tamanho de destino antes de executar a solicitação.

Evite usar wrapContentSize ou deixar dimensões sem restrições em elementos combináveis que carregam imagens remotas. Se essas bibliotecas não puderem inferir os limites de destino, elas vão carregar a imagem original em tamanho original. Isso pode levar ao carregamento de uma imagem muito maior do que o necessário, aumentando o uso de memória e a latência.

Em vez disso, defina dimensões explícitas no elemento combinável de imagem (por exemplo, usando Modifier.size) ou defina uma proporção. Isso permite que o mecanismo de layout calcule o destino exato de pixels antecipadamente, que o carregador de imagens pode usar para solicitar e decodificar o recurso do tamanho correto.

Forneça recursos alternativos para diferentes tamanhos de tela

Se você estiver enviando imagens com seu app, forneça recursos de tamanhos diferentes para resoluções de dispositivo distintas. Isso pode ajudar a reduzir o tamanho do download do app em dispositivos e melhorar a performance, já que uma imagem de resolução menor vai ser carregada em um dispositivo de resolução mais baixa. Para saber mais sobre como fornecer bitmaps alternativos para diferentes tamanhos de dispositivo, consulte a documentação alternativa de bitmap.

Não aplique padding diretamente

Às vezes, é necessário adicionar padding a uma imagem. Por exemplo, talvez você queira que a imagem seja cercada por uma borda transparente para letterboxing. Nessas situações, não adicione o padding diretamente à imagem, mudando as dimensões dela. Em vez disso, deixe as dimensões da imagem como estão e ajuste a localização dela na tela usando InsetDrawable. Outra opção é adicionar padding ao elemento combinável ou à visualização que contém a imagem.

Escolher o formato de pixel certo

Equilibre memória e qualidade escolhendo o formato de pixel certo. Use RGB_565 quando não precisar de transparência. Esse formato usa metade da memória do formato ARGB_8888 padrão.

No Glide, é possível configurar isso usando DecodeFormat. No Coil, você pode usar a propriedade bitmapConfig.

Use vetores sempre que possível

Para imagens compostas de formas geométricas, um gráfico vetorial é muito menor que um bitmap e é dimensionado sem problemas para qualquer densidade de tela. Quando adequado, use elementos como ShapeDrawable para representar gráficos.

Libere e reutilize bitmaps quando possível

Arquivos gráficos grandes podem ocupar muita memória. Para reduzir o impacto, libere ou reutilize os objetos gráficos sempre que possível.

Se você usar uma biblioteca de carregamento de imagens, libere os bitmaps para o pool gerenciado da biblioteca quando não precisar mais deles. A biblioteca pode reutilizar os objetos quando necessário e mantém um buffer de memória disponível para necessidades futuras.

Se você estiver gerenciando gráficos manualmente, libere bitmaps quando terminar de usá-los chamando Bitmap.recycle e descartando imediatamente a referência Bitmap, em vez de depender da coleta de lixo.

Outras dicas e sugestões

Esta seção lista algumas outras maneiras de melhorar a performance do app ao processar gráficos.

Não empacote imagens grandes com o arquivo AAB/APK

Uma das principais causas para o tamanho grande de download do app são os gráficos empacotados dentro do arquivo AAB ou APK. Use a ferramenta APK Analyzer para garantir que você não esteja empacotando arquivos de imagem maiores do que o necessário. Reduza os tamanhos ou considere colocar as imagens em um servidor e só fazer o download delas quando necessário.

Encontrar bitmaps redundantes

Se você tiver várias cópias da mesma imagem, isso vai desperdiçar memória. Você pode usar o criador de perfil do Android Studio para identificar gráficos redundantes. Use o analisador de heap dump para capturar um heap dump e filtre os resultados escolhendo a configuração bitmaps duplicados.

Ao usar ImageBitmap, chame prepareToDraw antes de renderizar

Ao usar ImageBitmap, para iniciar o processo de upload da textura para a GPU, chame ImageBitmap#prepareToDraw() antes da renderização real. Isso ajuda a GPU a preparar a textura e melhorar a performance da exibição de uma imagem na tela. A maioria das bibliotecas de carregamento de imagem já faz essa otimização, mas se você estiver trabalhando com a classe ImageBitmap por conta própria, é importante se lembrar disso.

Prefira transmitir um Int DrawableRes ou URL como parâmetros para o elemento combinável em vez de Painter

Devido às complexidades de lidar com imagens, por exemplo, codificar uma função igual a Bitmaps seria uma opção de computação muito cara, a API Painter não está explicitamente marcada como estável com a anotação @Stable. As classes instáveis podem levar a recomposições desnecessárias porque o compilador não pode inferir com facilidade se os dados mudaram.

Por isso, recomendamos transmitir um URL ou um ID de recurso drawable como parâmetros aos elementos combináveis, em vez de transmitir um Painter como parâmetro.

// Prefer this:
@Composable
fun MyImage(url: String) {

}
// Over this:
@Composable
fun MyImage(painter: Painter) {

}