È la fine del 2018, quindi le cose sono cambiate.
Prima di tutto: esegui la tua app e apri la scheda Profiler Android in Android Studio. Vedrai quanta memoria consuma, rimarrai sorpreso ma può allocare molta RAM.
Anche qui c'è un ottimo articolo in documenti ufficiali con istruzioni dettagliate su come usare Memory Profiler che può darti uno sguardo approfondito sulla tua gestione della memoria.
Ma nella maggior parte dei casi, il tuo normale profilo Android sarà sufficiente per te.
Di solito, un'app inizia con 50 MB di allocazione RAM ma salta all'istante fino a 90 Mb quando inizi a caricare alcune foto in memoria. Quando apri Activity con un ViewPager con foto precaricate (3,5 Mb ciascuna) puoi ottenere 190 Mb facilmente in pochi secondi.
Ma questo non significa che hai problemi con la gestione della memoria.
Il miglior consiglio che posso dare è seguire le linee guida e le migliori pratiche, usare le migliori librerie per il caricamento delle immagini (Glide, Picasso) e starai bene.
Ma se hai bisogno di personalizzare qualcosa e hai davvero bisogno di sapere quanta memoria puoi allocare manualmente puoi ottenere la memoria libera totale e calcolare una porzione predeterminata (in%) da essa. Nel mio caso, dovevo memorizzare nella cache le foto decodificate in modo da non doverle decrittografare ogni volta che l'utente scorre l'elenco.
A tale scopo è possibile utilizzare la classe LruCache pronta per l'uso . È una classe di cache che traccia automaticamente la quantità di memoria allocata dagli oggetti (o il numero di istanze) e rimuove la più vecchia per conservare la recente dalla cronologia di utilizzo.
Ecco un ottimo tutorial su come usarlo.
Nel mio caso, ho creato 2 istanze di cache: per pollici e allegati. Li ha resi statici con accesso singleton in modo che siano disponibili a livello globale in tutta l'app.
classe di 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;
}
}
Ecco come calcolo la RAM libera disponibile e quanto posso mordere:
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);
}
Ed è così che lo uso negli adattatori per ottenere l'immagine memorizzata nella cache:
byte[] decryptedThumbnail = BitmapLruCache.getDecryptedThumbCacheInstance().getBitmap(thumbUri);
e come l'ho impostato nella cache in thread in background (AsyncTask normale):
BitmapLruCache.getDecryptedThumbCacheInstance().addBitmap(thumbUri, thumbBytes);
La mia app ha come target API 19+, quindi i dispositivi non sono vecchi e queste porzioni di RAM disponibili sono abbastanza buone per la cache nel mio caso (1% e 3%).
Curiosità: Android non ha API o altri hack per ottenere la quantità di memoria allocata alla tua app, viene calcolata al volo in base a vari fattori.
PS Sto usando un campo di classe statica per contenere una cache, ma secondo le più recenti linee guida di Android è consigliato utilizzare il componente di architettura ViewModel a tale scopo.