É o fim de 2018, então as coisas mudaram.
Primeiro: execute o aplicativo e abra a guia Android Profiler no Android Studio. Você verá quanta memória consome, ficará surpreso, mas ele pode alocar muita RAM.
Também aqui está um excelente artigo em documentos oficiais, com instruções detalhadas sobre como usar o Memory Profiler, que pode fornecer uma visão detalhada do gerenciamento de memória.
Mas na maioria dos casos, seu Android Profiler regular será suficiente para você.
Normalmente, um aplicativo começa com 50Mb de alocação de RAM, mas salta instantaneamente até 90Mb quando você começa a carregar algumas fotos na memória. Quando você abre o Activity com um ViewPager com fotos pré-carregadas (3,5Mb cada), você pode obter 190Mb facilmente em segundos.
Mas isso não significa que você tenha problemas com o gerenciamento de memória.
O melhor conselho que posso dar é seguir as diretrizes e práticas recomendadas, usar as principais bibliotecas para carregamento de imagens (Glide, Picasso) e você ficará bem.
Mas, se você precisar adaptar algo e realmente precisar saber quanta memória pode alocar manualmente, poderá obter memória livre total e calcular uma parte pré-determinada (em%) dela. No meu caso, eu precisava armazenar em cache as fotos descriptografadas na memória para não precisar descriptografá-las sempre que o usuário passar pela lista.
Para esse fim, você pode usar a classe LruCache pronta para usar . É uma classe de cache que rastreia automaticamente a quantidade de memória que seus objetos alocam (ou o número de instâncias) e remove a mais antiga para manter as recentes pelo histórico de uso.
Aqui está um ótimo tutorial sobre como usá-lo.
No meu caso, criei 2 instâncias de caches: para polegares e anexos. Tornou-os estáticos com acesso único, para que estejam disponíveis globalmente em todo o aplicativo.
classe de cache:
public class BitmapLruCache extends LruCache<Uri, byte[]> {
private static final float CACHE_PART_FOR_THUMBS_PRC = 0.01f; // 1% (Nexus 5X - 5Mb)
private static final float CACHE_PART_FOR_ATTACHMENTS_PRC = 0.03f;// 3% (Nexus 5X - 16Mb)
private static BitmapLruCache thumbCacheInstance;
private static BitmapLruCache attachmentCacheInstance;
public static synchronized BitmapLruCache getDecryptedThumbCacheInstance() {
if (thumbCacheInstance == null) {
int cacheSize = getCacheSize(CACHE_PART_FOR_THUMBS_PRC);
//L.log("creating BitmapLruCache for Thumb with size: " + cacheSize + " bytes");
thumbCacheInstance = new BitmapLruCache(cacheSize);
return thumbCacheInstance;
} else {
return thumbCacheInstance;
}
}
public static synchronized BitmapLruCache getDecryptedAttachmentCacheInstance() {
if (attachmentCacheInstance == null) {
int cacheSize = getCacheSize(CACHE_PART_FOR_ATTACHMENTS_PRC);
// L.log("creating BitmapLruCache for Attachment with size: " + cacheSize + " bytes");
attachmentCacheInstance = new BitmapLruCache(cacheSize);
return attachmentCacheInstance;
} else {
return attachmentCacheInstance;
}
}
private BitmapLruCache(int maxSize) {
super(maxSize);
}
public void addBitmap(Uri uri, byte[] bitmapBytes) {
if (get(uri) == null && bitmapBytes != null)
put(uri, bitmapBytes);
}
public byte[] getBitmap(Uri uri) {
return get(uri);
}
@Override
protected int sizeOf(Uri uri, byte[] bitmapBytes) {
// The cache size will be measured in bytes rather than number of items.
return bitmapBytes.length;
}
}
É assim que eu calculo a RAM livre disponível e quanto posso extrair dela:
private static int getCacheSize(float partOfTotalFreeMemoryToUseAsCache){
final long maxMemory = Runtime.getRuntime().maxMemory();
//Use ... of available memory for List Notes thumb cache
return (int) (maxMemory * partOfTotalFreeMemoryToUseAsCache);
}
E é assim que eu o uso nos adaptadores para obter a imagem em cache:
byte[] decryptedThumbnail = BitmapLruCache.getDecryptedThumbCacheInstance().getBitmap(thumbUri);
e como eu o configuro no cache no thread de segundo plano (AsyncTask regular):
BitmapLruCache.getDecryptedThumbCacheInstance().addBitmap(thumbUri, thumbBytes);
Meu aplicativo tem como alvo a API 19+, para que os dispositivos não sejam antigos e essas partes da RAM disponível sejam boas o suficiente para cache no meu caso (1% e 3%).
Curiosidade: o Android não possui APIs ou outros hacks para obter a quantidade de memória alocada ao seu aplicativo, é calculado em tempo real com base em vários fatores.
PS: Estou usando um campo de classe estática para armazenar um cache, mas de acordo com as diretrizes mais recentes do Android, é recomendável usar o componente de arquitetura ViewModel para esse fim.