Ottimizzare le immagini bitmap

L'utilizzo delle immagini può introdurre rapidamente problemi di prestazioni se non presti attenzione. Anche un piccolo elemento grafico in un formato compresso come JPG o PNG può trasformarsi in una bitmap di grandi dimensioni quando viene decodificato per la visualizzazione. Se non utilizzi la grafica in modo efficiente, potresti riscontrare problemi di memoria che possono influire negativamente sulle prestazioni della tua app e di altre app sul dispositivo. Segui queste best practice per assicurarti che la tua app funzioni al meglio.

Utilizza le librerie di caricamento delle immagini

Puoi migliorare l'efficienza della tua app utilizzando librerie di caricamento delle immagini come Coil (per i progetti Kotlin-first) o Glide (per i progetti Java). Queste librerie riducono la memoria utilizzata dalla tua app eseguendo operazioni come la memorizzazione nella cache delle immagini, il downsampling della grafica quando necessario e il riciclo degli oggetti grafici.

Esegui il downsampling delle immagini

Assicurati di utilizzare le dimensioni dell'immagine appropriate per le tue esigenze. Dovresti evitare di caricare un'immagine grande ad alta risoluzione in un contenitore piccolo (ad esempio una miniatura). Utilizza invece il downsampling per ridurre le dimensioni dell'immagine prima di decodificarla in memoria.

Downsampling lato client

Le librerie di caricamento delle immagini come Coil e Glide gestiscono automaticamente il downsampling. Puoi configurare le strategie di downsampling utilizzando ImageLoader (per Coil) o DownsampleStrategy (per Glide). Se gestisci le bitmap manualmente, puoi utilizzare inSampleSize per decodificare una versione più piccola. Per farlo in sicurezza, devi prima impostare inJustDecodeBounds su true per leggere le dimensioni dell'immagine senza allocare memoria, calcolare le dimensioni del campione, impostare inSampleSize su quel valore, impostare inJustDecodeBounds su false e poi decodificare l'immagine.

Preferisci il ridimensionamento lato server

Se possibile, richiedi le dimensioni esatte dell'immagine di cui hai bisogno direttamente dal tuo server di backend. In questo modo, l'utilizzo della rete e l'impatto sulla cache del disco vengono ridotti, mentre la memoria utilizzata è più leggera, evitando l'overhead di memoria dovuto al ridimensionamento delle immagini sul dispositivo.

Puoi configurare le librerie in modo che aggiungano dinamicamente le dimensioni della visualizzazione di destinazione all'URL dell'immagine. Ad esempio, Coil lo consente utilizzando intercettatori personalizzati, mentre Glide lo supporta utilizzando caricatori di modelli personalizzati (come BaseGlideUrlLoader).

Evita dimensioni di layout non vincolate

Affinché i caricatori di immagini eseguano il downsampling (lato client o lato server) in modo efficace, devono conoscere le dimensioni target prima di eseguire la richiesta.

Evita di utilizzare wrapContentSize o di lasciare le dimensioni non vincolate su elementi componibili che caricano immagini remote. Se queste librerie non riescono a dedurre i limiti target, caricano l'immagine originale a grandezza originale. Ciò può comportare il caricamento di un'immagine sostanzialmente più grande del necessario, aumentando la memoria utilizzata e la latenza.

Imposta invece dimensioni esplicite sul composable dell'immagine (ad esempio, utilizzando Modifier.size) o definisci le proporzioni. In questo modo, il motore di layout può calcolare in anticipo il target esatto dei pixel, che il caricatore di immagini può poi utilizzare per richiedere e decodificare l'asset con le dimensioni corrette.

Fornisci risorse alternative per schermi di dimensioni diverse

Se la tua app include immagini, valuta la possibilità di fornire asset di dimensioni diverse per le diverse risoluzioni dei dispositivi. In questo modo, puoi ridurre le dimensioni di download della tua app sui dispositivi e migliorare le prestazioni, poiché verrà caricata un'immagine a risoluzione inferiore su un dispositivo con risoluzione inferiore. Per ulteriori informazioni su come fornire bitmap alternative per dispositivi di dimensioni diverse, consulta la documentazione relativa alle bitmap alternative.

Non applicare il padding direttamente

A volte potrebbe essere necessario aggiungere spaziatura interna a un'immagine. Ad esempio, potresti voler circondare l'immagine con un bordo trasparente per il letterbox. In questi casi, non aggiungere il padding direttamente all'immagine, modificandone le dimensioni. Lascia invece le dimensioni dell'immagine così come sono e regola la posizione dell'immagine sullo schermo utilizzando InsetDrawable. In alternativa, puoi aggiungere un padding al composable o alla visualizzazione che contiene l'immagine.

Scegli il formato dei pixel giusto

Bilancia la memoria e la qualità scegliendo il formato dei pixel giusto. Utilizza RGB_565 quando non hai bisogno della trasparenza; questo formato utilizza metà della memoria del formato ARGB_8888 predefinito.

In Glide puoi configurare questa opzione utilizzando DecodeFormat. In Coil, puoi utilizzare la proprietà bitmapConfig.

Utilizza i vettori, se possibile

Per le immagini composte da forme geometriche, una grafica vettoriale è molto più piccola di una bitmap e si ridimensiona in modo fluido per qualsiasi densità di visualizzazione. Se opportuno, utilizza elementi come ShapeDrawable per rappresentare la grafica.

Rilascia e riutilizza le bitmap quando possibile

I file grafici di grandi dimensioni possono occupare molta memoria. Per ridurre il loro impatto, devi rilasciare o riutilizzare gli oggetti grafici ogni volta che puoi.

Se utilizzi una libreria di caricamento delle immagini, assicurati di rilasciare le bitmap nel pool gestito della libreria quando non ne hai più bisogno. La libreria può riutilizzare gli oggetti quando necessario e mantiene un buffer di memoria disponibile per esigenze future.

Se gestisci la grafica manualmente, devi rilasciare le bitmap quando hai finito di utilizzarle chiamando Bitmap.recycle e ignorando immediatamente il riferimento Bitmap, anziché affidarti alla garbage collection.

Altri suggerimenti utili

Questa sezione elenca alcuni altri modi per migliorare le prestazioni della tua app durante la gestione della grafica.

Non includere immagini di grandi dimensioni nel file AAB/APK

Una delle cause principali delle grandi dimensioni di download delle app è la grafica pacchettizzata all'interno del file AAB o APK. Utilizza lo Strumento di analisi APK per assicurarti di non pacchettizzare file immagine più grandi del necessario. Riduci le dimensioni o valuta la possibilità di posizionare le immagini su un server e scaricarle solo quando necessario.

Trova bitmap ridondanti

Se hai più copie della stessa immagine, la memoria viene sprecata. Puoi utilizzare il profiler di Android Studio per identificare gli elementi grafici ridondanti. Utilizza l'analizzatore dump dell'heap per acquisire un dump dell'heap e filtra i risultati scegliendo l'impostazione bitmap duplicate.

Quando utilizzi ImageBitmap, chiama prepareToDraw prima di disegnare

Quando utilizzi ImageBitmap, per avviare il processo di caricamento della texture sulla GPU, chiama ImageBitmap#prepareToDraw() prima di disegnarla effettivamente. In questo modo, la GPU può preparare la texture e migliorare le prestazioni di visualizzazione di un elemento visivo sullo schermo. La maggior parte delle librerie di caricamento delle immagini esegue già questa ottimizzazione, ma se lavori direttamente con la classe ImageBitmap, è un aspetto da tenere presente.

Preferisci passare un DrawableRes Int o un URL come parametri nel tuo componibile anziché Painter

A causa della complessità della gestione delle immagini (ad esempio, la scrittura di una funzione equals per Bitmaps sarebbe costosa dal punto di vista computazionale), l'API Painter non è esplicitamente contrassegnata come stabile con l'annotazione @Stable. Le classi instabili possono portare a ricomposizioni non necessarie perché il compilatore non può dedurre facilmente se i dati sono cambiati.

Pertanto, ti consigliamo di passare un URL o un ID risorsa drawable come parametri al composable, anziché passare un Painter come parametro.

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

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

}