Observação: várias bibliotecas seguem as práticas recomendadas para carregar imagens. Você pode usar essas bibliotecas no seu app para carregar imagens de maneira mais otimizada. Recomendamos a biblioteca Glide, que carrega e exibe imagens da forma mais rápida e simples possível. Outras bibliotecas populares de carregamento de imagens incluem Picasso, do Square, Coil, do Instacart e Fresco, do Facebook. Essas bibliotecas simplificam a maioria das tarefas complexas associadas a bitmaps e outros tipos de imagens no Android.
Existem imagens de todos os formatos e tamanhos. Em muitos casos, elas são maiores que o necessário para uma interface de usuário (IU) típica de um app. Por exemplo, o app do sistema Galeria exibe fotos tiradas com a câmera dos seus dispositivos Android, que costumam ter uma resolução muito maior que a densidade de tela do dispositivo.
Como você está trabalhando com memória limitada, o ideal é carregar somente uma versão de resolução mais baixa na memória. A versão de resolução mais baixa precisa corresponder ao tamanho do componente de IU que a exibe. Uma imagem com uma resolução mais alta não fornece benefícios visíveis, mas ainda ocupa um espaço precioso na memória e causa sobrecarga adicional de desempenho devido ao dimensionamento adicional em tempo real.
Esta lição orienta você durante a decodificação de bitmaps grandes sem exceder ao limite de memória por app ao carregar uma versão menor subamostrada na memória.
Ler dimensões e tipo de bitmap
A classe BitmapFactory
fornece vários métodos de decodificação (decodeByteArray()
, decodeFile()
, decodeResource()
etc.) para criar um Bitmap
a partir de várias fontes. Escolha
o método de decodificação mais apropriado com base na sua fonte de dados de imagem. Esses métodos tentam
alocar memória para o bitmap construído e, portanto, podem facilmente resultar em uma
exceção OutOfMemory
. Cada tipo de método de decodificação tem assinaturas adicionais que permitem especificar opções
de decodificação por meio da classe BitmapFactory.Options
. Definir a propriedade inJustDecodeBounds
como true
durante a decodificação
evita a alocação de memória, retornando null
para o objeto bitmap, mas definindo outWidth
, outHeight
e outMimeType
. Essa técnica permite que você leia
as dimensões e o tipo dos dados de imagem antes da construção (e alocação de memória) do
bitmap.
Kotlin
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeResource(resources, R.id.myimage, options) val imageHeight: Int = options.outHeight val imageWidth: Int = options.outWidth val imageType: String = options.outMimeType
Java
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;
Para evitar exceções java.lang.OutOfMemory
, verifique as dimensões de um bitmap antes de
decodificá-lo, a menos que você confie totalmente na fonte para fornecer dados de imagem de tamanho previsível
que se encaixam perfeitamente na memória disponível.
Carregar uma versão reduzida na memória
Agora que as dimensões de imagem são conhecidas, elas podem ser usadas para decidir se a imagem carregada na memória será completa ou subamostrada. Veja alguns fatores a serem considerados:
- Estimativa de uso de memória ao carregar a imagem inteira.
- Quantidade de memória que você pretende comprometer para carregar essa imagem, considerando quaisquer outros requisitos de memória do seu app.
- Dimensões do
ImageView
ou componente de IU de destino em que a imagem será carregada. - Tamanho da tela e densidade do dispositivo atual.
Por exemplo, não vale a pena carregar uma imagem de 1024x768 pixels na memória se ela é
exibida em miniatura de 128x96 pixels em ImageView
.
Para solicitar que o decodificador faça uma subamostra da imagem, carregando uma versão menor na memória, defina inSampleSize
como true
no seu objeto BitmapFactory.Options
. Por exemplo, uma imagem com resolução 2048x1536 decodificada
com inSampleSize
de 4 produz um
bitmap de aproximadamente 512x384. Carregar isso na memória usa 0,75 MB em vez de 12MB para a imagem
completa (supondo uma configuração de bitmap de ARGB_8888
). Veja
um método para calcular um valor de tamanho de amostra que é uma potência de dois com base na largura e na altura de um alvo:
Kotlin
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { // Raw height and width of image val (height: Int, width: Int) = options.run { outHeight to outWidth } var inSampleSize = 1 if (height > reqHeight || width > reqWidth) { val halfHeight: Int = height / 2 val halfWidth: Int = width / 2 // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { inSampleSize *= 2 } } return inSampleSize }
Java
public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
Observação: uma potência de dois valores é calculada porque o decodificador usa
um valor final arredondando para baixo para a potência mais próxima de dois, conforme a documentação de inSampleSize
.
Para usar esse método, faça a decodificação com inJustDecodeBounds
definido como true
, transmita as opções
e depois decodificar novamente usando o novo valor inSampleSize
e inJustDecodeBounds
definido como false
:
Kotlin
fun decodeSampledBitmapFromResource( res: Resources, resId: Int, reqWidth: Int, reqHeight: Int ): Bitmap { // First decode with inJustDecodeBounds=true to check dimensions return BitmapFactory.Options().run { inJustDecodeBounds = true BitmapFactory.decodeResource(res, resId, this) // Calculate inSampleSize inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight) // Decode bitmap with inSampleSize set inJustDecodeBounds = false BitmapFactory.decodeResource(res, resId, this) } }
Java
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }
Esse método facilita o carregamento de um bitmap de tamanho arbitrariamente grande em um ImageView
que exibe uma miniatura de 100x100 pixels, conforme mostrado no seguinte código de
exemplo:
Kotlin
imageView.setImageBitmap( decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100) )
Java
imageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
É possível seguir um processo semelhante para decodificar bitmaps de outras fontes, substituindo o
método BitmapFactory.decode*
apropriado conforme necessário.