La gestione della memoria delle bitmap è fondamentale per mitigare i rischi di Out-Of-Memory (OOM) nelle app per Android con contenuti visivi complessi. Anche se gli sviluppatori sanno che devono ridurre le dimensioni delle immagini, per ottenere una stabilità dell'app a lungo termine è necessario padroneggiare altri due pilastri fondamentali: il riutilizzo e il riciclo. Questa sessione offre un playbook pratico per ottimizzare le prestazioni della memoria utilizzando strumenti moderni, librerie open source come Glide e Coil e API a livello di sistema.
In questa sessione imparerai a diagnosticare le regressioni della memoria reali utilizzando Android vitals, a implementare pattern di memorizzazione nella cache avanzati (riutilizzo) con Glide e Coil e a rilasciare la memoria in modo proattivo (riciclo) utilizzando il callback onTrimMemory.
BitmapRegionDecoder anziché caricare immagini a risoluzione piena di dimensioni eccessive.onTrimMemory. Interrompi il polling e le animazioni in background in onStop per evitare l'aumento della memoria in background.Trascrizione
Fung Lam
Sì, manca circa un'ora al pranzo, quindi restate con me.
Sono Fung Lam, Developer Relations Engineer di Android, con sede qui a Londra.
Sì, grazie.
Prima di iniziare, posso farti una domanda?
Quanti di voi hanno acquistato un nuovo smartphone quest'anno?
Wow.
Wow.
Sii onesto, non ti biasimerò se non acquisterai Pixel.
Beh, il nuovo smartphone è più costoso, giusto?
Sì, è per questo che alcuni di voi non hanno acquistato un nuovo smartphone.
Perché è più costoso?
Lo sai?
No.
RAM.
RAM, sì, sì.
Sì, la RAM è davvero la sfida per Android e per noi, per gli sviluppatori.
Per questo oggi sono qui per spiegarvi come possiamo superare la sfida della memoria, della RAM.
In precedenza, durante Google I/O e Google I/O Connect, abbiamo tenuto alcune sessioni sulla memoria, su come il sistema operativo alloca la memoria, su come profilare l'applicazione, su come ottimizzare il codice utilizzando R8 e, naturalmente, ho parlato un po' di Bitmap in quel momento.
Potresti vedere le mie foto nello screenshot.
L'ottimizzazione della memoria è un dominio di grandi dimensioni, che abbraccia diverse aree come Android Runtime, garbage collection, strumenti per le prestazioni, allocazione della memoria degli oggetti, compilatore e così via. In questa sessione di 30 minuti, ti darò alcune informazioni su come ottimizzare la memoria delle bitmap.
Potresti chiederti: perché Bitmap?
Perché dovremmo concentrarci sulla memoria delle bitmap?
Perché un'immagine vale più di mille parole.
È vero, ti spiegherò il perché.
Su uno schermo di uno smartphone, ad esempio Pixel 7, Pixel 9 o Pixel 11 Pro, la risoluzione dello schermo è di circa 2500 x 1200 o 1400.
Quindi, per ogni pixel, abbiamo 32 bit di profondità di colore.
Quindi, per l'immagine dello schermo intero, oh, non ho abbastanza dita, ma, ad esempio, occuperà circa 15 MB di memoria, 50 MB.
Quindi, una parola nel sistema operativo è di 64 bit, ovvero 8 byte.
Mille parole sono solo 8 MB.
Sì, 8K.
Quindi, un'immagine vale sicuramente più di mille parole.
Sì, è importante.
Un'immagine ha già una dimensione di 15 MB.
Cosa succede se la tua app ha 10 o 100 immagini?
Beh, è un numero elevato.
In realtà, Bitmap è sempre, di solito, il più grande consumatore di memoria nella tua applicazione.
Quando l'immagine viene decodificata in memoria, come ho già detto, viene espansa in base alle dimensioni e alla profondità di colore.
Non si tratta di sapere se le dimensioni del file sono in formato JPEG o PNG.
No, verrà decodificato e utilizzerà la memoria.
Pertanto, bastano poche immagini a risoluzione massima per occupare facilmente centinaia di megabyte della memoria del dispositivo.
In secondo luogo, quando la memoria è molto limitata, il sistema operativo deve consumare più energia per gestire il consumo di memoria.
Questo darà l'impressione che la UI sia instabile.
In alternativa, il sistema operativo potrebbe anche terminare l'app per liberare memoria.
In caso contrario, l'app riscontrerà anche un errore di memoria insufficiente, un'eccezione di memoria insufficiente.
Fortunatamente, la buona notizia è che questa fase di solito è un grande successo con poco sforzo.
La maggior parte dei picchi di memoria deriva da errori comuni, che potrebbero essere evitati, e potremmo risolverli senza alcun compromesso sulla qualità dell'immagine.
Ora sai che la memoria bitmap è importante per la tua applicazione, non solo per renderla fluida, ma anche per la stabilità e la fidelizzazione utenti.
Oggi vedremo cinque semplici passaggi per garantire il corretto funzionamento della tua app.
Innanzitutto, esamineremo la qualità tecnica di Google Play, il nuovo requisito di qualità tecnica, che include il limite di memoria imminente e dove puoi controllarlo facilmente.
In secondo luogo, condividerò un case study su circa 500 app per Android, su come utilizzano la memoria, se lo fanno nel modo giusto o se c'è qualcosa che possiamo imparare.
In seguito, ti mostrerò il principio su come ottimizzare la memoria utilizzata per le bitmap.
Infine, condivideremo alcuni strumenti che potrebbero aiutarti a controllare la tua app.
Fung Lam
Innanzitutto, il nuovo requisito di qualità di Google Play.
Con che frequenza visiti Google Play Console?
Ogni mese?
Ogni settimana?
Mai?
Le risate significano sì, giusto?
No?
No.
Hai visitato la sezione Android vitals?
Sì, sì.
Sì, quindi sai sicuramente qualcosa prima che io lo faccia.
Pertanto, su Google Play sono presenti nuovi requisiti di qualità relativi alla soglia di rendimento.
Noi di Android e Play vogliamo che gli utenti utilizzino app di buona qualità.
Quindi, anche questa soglia è un elemento che cerchiamo di ottimizzare per l'utente finale.
In parole semplici, "Ehi, quando la tua app è in esecuzione in background o in una superficie in primo piano osservabile, il 90° percentile della memoria utilizzata non deve superare questa soglia.
In caso contrario, la visibilità della tua app sul Play Store potrebbe essere ridotta o altro."
L'applicazione inizierà a febbraio 2027 su app e giochi.
Il superamento di questa soglia influisce direttamente sulla valutazione dell'integrità della tua app.
Quindi, assicurati di andare su Google Play Console e controllare: "Qual è il mio 90° percentile oggi?"
E apporta il miglioramento prima della scadenza.
Questa è l'animazione.
"Hey, se vai su Google Play Console, puoi vedere, sì, Android vitals.
C'è una parte di memoria.
Puoi vedere il 90° percentile.
Se ci fai clic sopra, puoi vedere nel tempo la media del consumo di memoria.
Esistono anche diversi livelli per RAM, fattore di forma, densità dello schermo e versione del sistema operativo, che ti aiutano a isolare i colli di bottiglia della memoria su dispositivi entry-level specifici o su release di app specifiche.
Ora, so che potresti dire: "Ehi, grazie per avermi detto che le mie app hanno superato la soglia.
Ma in realtà non so cosa ci sia che non va.
Ci aiuteresti?"
Sì, è per questo che sono qui per aiutarti.
Cercherò di semplificarti le cose. Ad esempio, se stai guardando la TV e non vedi il video o non senti l'audio, cosa fai?
Prendi il manuale della TV, vai alle ultime pagine, dove di solito si trova la guida alla risoluzione dei problemi, che dice: "Se la TV non ha audio, prova questo o quel pulsante".
Sì, allo stesso modo, oggi ho identificato alcuni problemi comuni.
E, si spera, questa è la tua guida alla risoluzione dei problemi.
Normalmente, quando leggiamo il manuale della TV, dovremmo iniziare dalla prima pagina, che di solito contiene i toolkit diagnostici per aiutarci a capire come funziona la TV.
Ma ora che so che sono meno interessanti, oggi permettetemi di passare prima alla risoluzione dei problemi e torneremo su questo argomento più tardi.
E, a dire il vero, è proprio così che leggo il manuale della TV.
Per scoprire il problema più frequente relativo alla memoria bitmap di allocazione bitmap nell'ecosistema Android, ho esaminato circa 500 app, tra le più popolari, in Android.
Eseguo l'app, la avvio, accedo, vado su pagine diverse, approfondisco e torno indietro.
E testo l'app su RAM diverse, anche su dispositivi diversi, come un dispositivo Android Go con 2 GB di memoria, alcuni dispositivi di livello base da 4 GB, alcuni di livello intermedio da 4, 6 GB a 8 GB o alcuni pieghevoli premium con uno schermo molto grande, un'ottima qualità dello schermo e 16 GB di memoria.
Abbiamo misurato la memoria utilizzata su questo dispositivo in diversi stati dell'app, acquisendo il dump dell'heap e utilizzando diversi strumenti, tra cui Perfetto AI Skill, per analizzare l'allocazione e identificare gli anti-pattern comuni, ovvero gli errori comuni che commettiamo su larga scala.
Ecco come è cambiata la memoria nei diversi stati di attività.
Con questa analisi, identifichiamo oltre 15 problemi comuni, che vanno dalle immagini di dimensioni eccessive all'aumento delle cache in background.
Questo è il case study principale che condivideremo ed esploreremo oggi.
Fung Lam
Sì, l'intero case study che sto condividendo ora riguarda la ricerca su 500 app.
Dato che siete qui, penso che tutti noi amiamo Android, giusto?
Tutti amiamo Android.
Supponiamo che uno di noi ami Android, ma non sia un esperto di sviluppo Android.
Quindi, crea un'app per Android per ordinare statuette Android.
Ma ci sono alcuni errori.
Parliamone. Vediamo, forse questo è il primo errore.
"Hey, in the product page, we have a two-column layout showing different Android figurine.
Per ogni colonna, l'immagine ha una dimensione di circa 400 x 400 pixel.
Tuttavia, l'app sta scaricando un'immagine dal server, indipendentemente dalle dimensioni dello schermo.
Viene scaricata un'immagine di 3000 x 3000 pixel.
Sì, perché lo sviluppatore dice: "Ehi, Android è così bello, così fantastico.
Voglio mostrare il meglio all'utente."
In realtà no, perché è limitato dalla dimensione della visualizzazione dell'immagine.
Quindi, anche se scarichi l'immagine di grandi dimensioni, non viene mostrata nel modo migliore all'utente.
Inoltre, questa immagine di grandi dimensioni ha richiesto memoria aggiuntiva.
Anche se eseguiamo il downsampling, il processo di downsampling utilizza memoria aggiuntiva.
Come possiamo aiutare questo sviluppatore a risolvere il problema?
Se conosci le CDN, saprai che possono gestire alcune operazioni sulle immagini, come il ritaglio, il ridimensionamento, la rotazione, l'applicazione di filtri e la trasformazione di un'immagine da quadrata a rotonda.
In base alla richiesta HTTP, la CDN potrebbe gestire qualcosa.
Quindi, anche se il tuo lato server ospita una sola immagine, puoi utilizzare la CDN per inviare un'immagine ridimensionata al tuo dispositivo.
Quindi, numero 1, sì, quando richiedi l'immagine, prova a utilizzare la CDN, in modo da richiedere l'immagine con le dimensioni di ImageView.
Ma se non è possibile, ad esempio se a volte potrebbe essere un po' più grande di ImageView, ricordati di eseguire il downsampling per ridimensionarla nel codice.
Quindi, quando viene caricata nella memoria, viene caricata un'immagine più piccola, non l'immagine a risoluzione piena.
Ecco un esempio di codice per Coil.
// 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"
// 2. Enforce exact downsampling + Hardware Bitmap config
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(sizedUrl)
.size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
.precision(Precision.EXACT) // Downsample to exact card size
.bitmapConfig(Bitmap.Config.HARDWARE)
.build(),
contentDescription = product.title
)Puoi passare la dimensione come testo in giallo.
Sì, puoi passare la dimensione come HTTP in una richiesta HTTP.
E poi, qui, nel codice evidenziato, vedi l'evidenziazione? Sì, sì, non utilizzare Size.ORIGINAL.
Sì, utilizza la larghezza e l'altezza, in modo che vengano passate come parametro.
Utilizza la precisione per eseguire il downsampling dell'immagine alle dimensioni esatte.
Se possibile, utilizza Bitmap.Config.HARDWARE, che utilizza anche meno memoria, poiché l'immagine verrà archiviata direttamente nella VRAM.
Analogamente, su questa app, ad esempio, viene rilasciata una nuova versione di oggetti da collezione Android, quindi è presente un'immagine lunga che l'utente può scorrere verso sinistra e verso destra per vedere i diversi design della nuova figura Android.
Ma c'è anche un altro problema.
Per l'immagine lunga, gran parte di questa si trova oltre lo schermo, il che significa che l'utente non può vederla, ma l'immagine viene caricata nella memoria.
Potremmo fare di meglio?
Sì.
Potremmo suddividerla in un'immagine più piccola e mostrare solo l'immagine visualizzata sullo schermo.
Quelli oltre lo schermo non verranno caricati o verranno scaricati.
Quindi, utilizzerà meno memoria.
Analogamente, in questo caso, ha risparmiato circa il 60% della memoria.
Naturalmente, se ritagli un rettangolo piccolo, risparmierai di più.
Ma allo stesso modo, se un utente vuole fornire una funzionalità per l'utente, ad esempio, puoi aumentare lo zoom per vedere ogni dettaglio della statuetta Android, un errore comune è caricare, di nuovo, un'immagine molto grande, in modo che possa essere ingrandita e rimpicciolita.
Ma non è necessario.
Un modo migliore è quando l'utente aumenta lo zoom sull'immagine originale a bassa risoluzione già caricata e, quando l'utente smette di pizzicare, calcoli le coordinate e poi carichi l'immagine a risoluzione completa, caricando solo quell'area, un'area calcolata dall'immagine ad alta risoluzione.
Pertanto, solo l'area visibile verrà caricata nella memoria.
Come si fa?
Esiste un'API chiamata BitmapRegionDecoder, che ti aiuta a caricare una regione specifica di un'immagine.
E, naturalmente, ci sono anche alcuni strumenti e librerie che potrebbero aiutarti.
Puoi cercarlo su GitHub.
Alcune app di fumetti o alcune mappe utilizzano già questa funzionalità.
Puoi usarli come riferimento.
Inoltre, siamo umani e commettiamo errori.
Anche l'AI commette errori.
Quindi, per evitare di commettere errori, potremmo anche aggiungere un limite quando carichiamo l'immagine.
Ad esempio, in questo caso, potrei dire: "Hey, usiamo le dimensioni della finestra o dello schermo come limite e inseriamole in una funzione min, min della larghezza della richiesta o della larghezza del limite".
// Query active window bounds instead of hardcoding 1080 / 1920
val bounds = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(activity).bounds
// Clamp target dimensions so Bitmap NEVER exceeds the active window
val safeWidth = minOf(requestedWidthPx, bounds.width())
val safeHeight = minOf(requestedHeightPx, bounds.height())
val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)Quindi, con questa funzione, ogni volta che carico un'immagine, non supererà mai il confine.
Quindi, non caricherà un'immagine più grande della risoluzione dello schermo.
Ricorda di decodificare solo la regione visibile dell'immagine per risparmiare memoria.
Fung Lam
E ancora, dire: "Ehi, questa app, in realtà, non è di uno sviluppatore.
In realtà, è per più collaboratori che potrebbero utilizzare strumenti diversi.
Ad esempio, una parte consiste nell'utilizzare, oh scusa.
Dicevamo.
Lo stesso vale per questa app.
Nella pagina di prodotto, ho un elenco di prodotti e ho visto questa immagine, che ho aggiunto al carrello degli acquisti.
Pochi minuti dopo, vado al pagamento e, guarda un po', appare di nuovo la stessa immagine.
Ma si tratta davvero della stessa immagine o, in realtà, di due oggetti diversi nella memoria?
Sì, uno degli errori più comuni che abbiamo riscontrato è che l'app carica molte immagini.
Tuttavia, nella memoria sono presenti anche molte immagini duplicate.
Se utilizzi il caricatore di immagini, sì, in realtà potresti passare una chiave per eseguire la memorizzazione nella cache.
In questo modo, quando viene utilizzata la stessa immagine, puoi utilizzare l'oggetto Bitmap già allocato anziché crearne uno nuovo.
In Android Studio, puoi utilizzare Memory Profiler per scoprire se la tua app ha bitmap duplicati.
Ho trattato questo argomento nella mia sessione di Google I/O, quindi puoi tornare su YouTube per guardare il video.
Se non utilizzo Android Studio, esistono anche comandi ADB per acquisire il dump dell'heap e poi posso utilizzare il linguaggio SQL del grafico heap di Perfetto per verificare se la mia app contiene bitmap duplicati.
Questo è un esempio di codice in Coil, come faccio ad aggiungere una chiave per la memorizzazione nella cache delle bitmap.
// Strip volatile query tokens (?from=list / ?sig=...) for cache key
val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))
// On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
AsyncImage(
model = ImageRequest.Builder(context)
.data(productUrl)
.placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
.memoryCacheKey(canonicalKey)
.diskCacheKey(productUrl.substringBefore("?"))
.build(),
contentDescription = product.title
)E ora, sì.
Torniamo all'esempio precedente.
Hey, la mia app ha uno sviluppatore diverso.
Alcune parti utilizzano le visualizzazioni, quindi utilizzano Glide come caricatore di immagini.
Alcune di queste app utilizzano Coil perché usano Compose.
Quindi, come hai appena detto, il caricatore di immagini ci aiuta a eseguire la memorizzazione nella cache, quindi c'è qualcosa di sbagliato se ho più di un caricatore di immagini?
Sicuramente c'è qualcosa che non va.
Per il caricatore di immagini, per impostazione predefinita, verrà utilizzata una percentuale X della dimensione della memoria heap come dimensione predefinita della cache.
Quindi, se hai un caricatore di immagini, viene utilizzato l'X% della memoria.
Se hai due caricatori di immagini, la memoria sarà raddoppiata.
Se hai tre caricatori di immagini, il costo sarà 3 volte superiore.
Se il caricatore di immagini non condivide lo stesso pool, si verificherà la stessa cosa.
Ad esempio, nella pagina di prodotto carico un'immagine utilizzando Glide, mentre nella pagina di checkout la stessa immagine verrà caricata utilizzando Coil.
Tuttavia, nel ricordo saranno presenti due immagini.
Non va bene.
Sì, quindi per risolvere il problema, idealmente, utilizziamo un solo caricatore di immagini o un pool condiviso per la memorizzazione nella cache.
Ma se questo richiede molto lavoro di ingegneria, non potrei farlo.
Oh, una soluzione più semplice sarà: hey, misuro la percentuale dell'immagine che verrà gestita da questo caricatore di immagini, la percentuale dell'immagine che verrà gestita da un altro caricatore di immagini e imposto il peso per un caricatore di immagini diverso, proprio come questo codice.
val totalBudgetBytes = computeAdaptiveCacheBytes(context)
val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)
// Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
val loaderABudget = (totalBudgetBytes * 0.70).toInt()
val loaderBBudget = (totalBudgetBytes * 0.30).toInt()
// Approach B (Simple Fix without stats): Divide by number of loaders!
val simpleSplitBudget = totalBudgetBytes / numberOfLoaders
MemoryCache.Builder()
.maxSizeBytes(simpleSplitBudget)
.build()Potrei impostare loaderABudget su 70% e loaderBBudget su 30%. Questo è un altro modo.
Se non sai davvero quante immagini verranno gestite da ogni caricatore, è comunque meglio dividere.
Se ho due caricatori di immagini, divido per 2; se ne ho tre, divido per 3, anziché utilizzare il valore predefinito, perché la memoria verrebbe utilizzata in modo eccessivo.
Supponiamo che, nella pagina di prodotto, ci aspettiamo che l'utente scorra per visualizzare altri prodotti.
Quindi, normalmente, potremmo dire: "Ehi, memorizziamo nella cache circa 50 immagini o 50 MB di immagini".
Ma cosa succede se l'app passa in background?
Se l'app è in background, l'utente potrebbe non navigare.
Se l'utente non sta navigando, devo comunque conservare questa quantità di immagini memorizzate nella cache?
No, dovremmo rilasciarlo.
Sì, dovremmo rispettare il callback onTrimMemory.
Sì, in realtà molte app al momento non implementano o non rispettano il callback onTrimMemory.
Per il callback onTrimMemory, dobbiamo prestare attenzione a due indicatori.
Uno è UI_HIDDEN, che è come se l'utente avesse premuto il pulsante Home, e non è immediatamente dopo aver premuto il pulsante Home, è un periodo di tempo dopo aver premuto il pulsante Home, il che significa: "Oh, la tua app è effettivamente in uno stato in cui non è presente un'interfaccia utente visibile all'utente".
E potremmo aspettarci: "Ehi, l'utente non utilizza la tua app.
Dovresti rilasciare le risorse al sistema operativo."
Quindi dobbiamo ridurre le dimensioni della cache.
L'altro è TRIM_MEMORY_BACKGROUND, il che significa che l'app è già in background da molto tempo e ora il sistema operativo ha davvero bisogno di alcune risorse.
In questa fase, devi liberare la memoria il più possibile, quasi al 100%.
In questo modo, riduci anche la probabilità e il rischio che la tua app venga interrotta dal sistema operativo.
Perché è come dire: "Ehi, vogliono memoria, tu gliela dai e poi il sistema operativo la gestisce".
In caso contrario, il sistema operativo non ha scelta e deve intervenire.
Fung Lam
Un altro errore importante che vedo è: "Ehi, ho appena detto che la tua app, se va in background, dovrebbe utilizzare meno memoria".
Tuttavia, ho notato che le app utilizzavano effettivamente più memoria in background.
Perché?
Ecco perché faccio ricerche.
Ho cercato di capire il motivo.
Uno dei motivi è: "Ehi, nella loro app hanno un'animazione o un carosello.
Ad esempio, scaricano una nuova immagine ogni 15 secondi e mostrano una nuova immagine all'utente."
Tuttavia, lo sviluppatore non ha interrotto l'animazione quando l'app passa in background.
Quindi, anche se l'app è in background, il codice è ancora in esecuzione, sta ancora ricevendo una nuova immagine, la sta decodificando e sta utilizzando più memoria.
Quindi, questa è un'operazione che l'app non dovrebbe eseguire.
Proprio così.
Dovremmo interrompere tutte le animazioni, la memorizzazione nella cache o le attività non correlate quando l'app passa in background.
Quindi, se conosci il ciclo di vita dell'attività, sai che esistono gli eventi onPause, onStop, assicurati di implementare onStop, onPause, onDestroyView per interrompere l'animazione, sì, per interrompere le richieste di pull delle immagini o altre cose relative al lavoro che non richiedono l'attenzione dell'utente.
override fun onStop() {
super.onStop()
// 1. Stop animations so they don't decode next frames in background!
bannerAnimationView.cancelAnimation()
animatedImageDrawable?.stop()
// 2. Cancel next-image buffering / prefetch queues!
nextImagesBufferingJob?.cancel()
Glide.with(this).pauseRequests()
}
override fun onDestroyView() {
super.onDestroyView()
bannerAnimationView.clearAnimation() // Release resources!
}Fung Lam
So di aver parlato di molti casi con te, ma ho detto: "Ho più di 15 casi".
Se ti parlassi di tutti i casi, potresti dimenticare tutto.
Quando torni a casa, cerchi su Google: "Hey, come faccio a risolvere i miei problemi di memoria?".
Quindi ti aiuto a ricordare.
Proprio così.
Li collegherò a un framework 3R molto noto per aiutarti a memorizzarli.
Penso che tutti dovrebbero conoscere questo framework 3R.
Il primo è la riduzione, ovvero come ridurre la memoria durante il caricamento di un'immagine.
Naturalmente, come ho già detto, potremmo eseguire il downsampling e ottenere la dimensione corretta dell'immagine quando la riceviamo dal server.
Ottimizziamo la profondità del colore o utilizziamo la bitmap hardware, se possibile.
Se non utilizzi una CDN, prova a farlo.
Quindi, quando recuperiamo l'immagine dal server, viene recuperata solo l'immagine più adatta alle dimensioni.
Riutilizzo: utilizziamo la cache in modo che l'immagine caricata in memoria venga riutilizzata e non ci siano immagini bitmap duplicate nella memoria.
E, naturalmente, cerca di non avere più caricatori di immagini nel codice, perché è meno efficiente.
Riduci, riutilizza e qual è l'ultimo?
Lo sai?
Ricicla.
Riprovare?
Ricicla, sì, ricicla.
Sì, abbiamo imparato a ridurre, riutilizzare e riciclare alle scuole elementari, anche se non abbiamo imparato a programmare.
Sì, ricicla, sì.
Sì, si tratta di come liberare la memoria in modo che un'altra applicazione o il sistema operativo possano utilizzarla.
Ci sono cose come il riciclo.
Innanzitutto, ho menzionato l'evento onTrimMemory, che indica che il sistema operativo ti invierà due segnali quando la tua UI è nascosta o quando il sistema operativo ha davvero bisogno di risorse.
Sì, prova a gestire questa richiesta.
E, naturalmente, se hai un'animazione, un caricatore di immagini o un altro lavoro in primo piano, assicurati di interromperlo quando si verifica un evento onPause, onStop.
Quindi riduci, riutilizza, ricicla.
Facile da ricordare, no?
Fung Lam
Ora torniamo alla parte meno interessante del manuale della TV.
Come utilizziamo i diversi toolkit per rilevare e misurare efficacemente questo problema di memoria?
Il primo è Google Play Console, Android vitals e l'API Reporting.
Sì, controlla il 90° percentile in Google Play Console.
Dove sei adesso?
In alternativa, utilizza l'API Reporting se non visiti la pagina web.
Puoi comunque utilizzare l'API per ottenere il valore.
Sì, assicurati che la soglia sia di 200 MB e 400 MB.
Assicurati di non superare la soglia.
Esistono anche comandi ADB che ti aiutano, ad esempio, ad acquisire il dump dell'heap o a inviare un evento per simulare che la tua app si trova ora in una fase UI_HIDDEN.
adb shell dumpsys meminfo <pkg> # watch Native Heap!
adb shell am send-trim-memory <pkg> UI_HIDDENSe utilizzi Android Studio, ottimo.
Tuttavia, è sempre un buon amico per te.
Puoi utilizzare il profiler di Android Studio per acquisire il dump dell'heap, in modo da avere un'interfaccia utente per vedere qual è effettivamente la memoria utilizzata e l'allocazione della memoria.
Naturalmente, parliamo di AI con Gemini in Android Studio o di altre skill Android disponibili.
Ti aiuta ad analizzare il dump dell'heap e a utilizzare le tracce SQL di Perfetto per trovare la bitmap e identificare normalmente il proprietario della cache.
L'immagine viene caricata da Glide o da Coil?
Ci sono altre perdite di memoria nella mia applicazione?
So che alcuni di voi dicono: "In realtà non utilizzo più l'IDE.
Sono un prompt engineer.
Sì, invio semplicemente un prompt con l'agente AI per scrivere il codice."
Sì, lo so.
Nel tempo libero mi dedico anche al vibe coding.
Quindi, condivido questo con voi solo come esempio.
Sì, questa è solo una parte del prompt che utilizzo per la mia ricerca su 500 app.
Sì, solo una parte.
Sai che lo schermo non è grande.
Se ne mettessi di più, si vedrebbe a malapena.
Ma questo ti dà un'idea di come potresti utilizzare l'AI per automatizzare, acquisire la memoria utilizzata, incluso il dump dell'heap, e analizzare e fornirti i risultati.
E sì, potrei dire qui, in un'altra frase, "Forniscimi anche la correzione del codice per il mio codice".
Spero che questo possa ridurre i tuoi timori, ad esempio: "Ehi, la memoria è un dominio di grandi dimensioni.
All'università non ho studiato sistemi operativi o architettura dei sistemi informatici in informatica.
Ma in realtà non è così spaventoso, non è così difficile."
Sì, è facile analizzare la tua app.
Prova.
Ma, ovviamente, l'AI commetterà errori, quindi la conoscenza fondamentale di come viene utilizzata la memoria e di come il sistema operativo la alloca è ancora importante.
Ecco perché ti invito a partecipare al mio intervento e a guardare il nostro video su YouTube.
Fung Lam
Quindi, per ricapitolare, abbiamo alcuni concetti chiave.
Innanzitutto, riduci.
Assicurati di ottenere le dimensioni corrette dell'immagine.
Dividi l'immagine in immagini più piccole per non caricare tutto nella memoria, ma solo l'immagine visibile all'utente.
Utilizza l'hardware bitmapConfig o RGB_565 quando possibile.
Riutilizza, quindi assicurati di avere una strategia di memorizzazione nella cache e utilizza il caricatore di immagini per ridurre il numero di bitmap duplicate nella memoria.
E, naturalmente, non utilizzare più immagini bitmap, anche se hai provato a sovrascrivere le dimensioni predefinite della cache di questi caricatori di immagini.
Non riutilizziamo né ricicliamo.
Proprio così.
La gestione della memoria dell'app deve adattarsi allo stato dell'app.
Se la UI è nascosta, sì, dovresti utilizzare meno memoria.
Se il sistema operativo ti chiede davvero di liberare memoria, liberane il più possibile.
E smettere di consumare le risorse, che si tratti di bitmap, energia o risorse di calcolo.
Sì, interrompi il timer del banner, l'animazione e il pooling delle immagini.
Infine, assicurati di monitorare.
Visita regolarmente Google Play Console.
Assicurati che questa sia una pratica.
Sì, questo ti aiuterà a capire qual è lo stato attuale del 90° percentile della memoria utilizzata dalla tua app nell'intera popolazione di utenti.
In questo modo, potrai anche identificare tempestivamente i problemi.
Proprio così.
Per la profilazione locale, puoi utilizzare Memory Profiler di Android Studio.
Potremmo Abbiamo anche un comando ADB.
Abbiamo anche una skill per Android.
Tuttavia, se vuoi visualizzare le statistiche relative agli utenti reali, Google Play Console è il posto giusto.
Inoltre, l'API Google Play Reporting ti consente di ottenere questi dati tramite API senza dover accedere a Play Console.
Come sempre, se vuoi saperne di più, visita developer.android.com/memory.
Riduci, riutilizza e ricicla con questo.
Potremmo creare un ecosistema Android migliore per superare la sfida che stiamo affrontando.
Grazie e buona programmazione.
A breve sarò disponibile per un incontro, quindi se hai domande, non esitare a venire.
E il prossimo relatore, beh, abbiamo un po' di tempo, quindi attendete che il prossimo relatore salga sul palco e grazie mille.
// 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"
// 2. Enforce exact downsampling + Hardware Bitmap config
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(sizedUrl)
.size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
.precision(Precision.EXACT) // Downsample to exact card size
.bitmapConfig(Bitmap.Config.HARDWARE)
.build(),
contentDescription = product.title
)// Query active window bounds instead of hardcoding 1080 / 1920
val bounds = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(activity).bounds
// Clamp target dimensions so Bitmap NEVER exceeds the active window
val safeWidth = minOf(requestedWidthPx, bounds.width())
val safeHeight = minOf(requestedHeightPx, bounds.height())
val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)// Strip volatile query tokens (?from=list / ?sig=...) for cache key
val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))
// On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
AsyncImage(
model = ImageRequest.Builder(context)
.data(productUrl)
.placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
.memoryCacheKey(canonicalKey)
.diskCacheKey(productUrl.substringBefore("?"))
.build(),
contentDescription = product.title
)val totalBudgetBytes = computeAdaptiveCacheBytes(context)
val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)
// Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
val loaderABudget = (totalBudgetBytes * 0.70).toInt()
val loaderBBudget = (totalBudgetBytes * 0.30).toInt()
// Approach B (Simple Fix without stats): Divide by number of loaders!
val simpleSplitBudget = totalBudgetBytes / numberOfLoaders
MemoryCache.Builder()
.maxSizeBytes(simpleSplitBudget)
.build()override fun onStop() {
super.onStop()
// 1. Stop animations so they don't decode next frames in background!
bannerAnimationView.cancelAnimation()
animatedImageDrawable?.stop()
// 2. Cancel next-image buffering / prefetch queues!
nextImagesBufferingJob?.cancel()
Glide.with(this).pauseRequests()
}
override fun onDestroyView() {
super.onDestroyView()
bannerAnimationView.clearAnimation() // Release resources!
}adb shell dumpsys meminfo <pkg> # watch Native Heap!
adb shell am send-trim-memory <pkg> UI_HIDDEN