Ottimizzare le immagini bitmap

Se non presti attenzione, lavorare con le immagini può causare rapidamente problemi di prestazioni. Anche una piccola immagine in un formato compresso come JPG o PNG può trasformarsi in una bitmap di grandi dimensioni quando viene decodificata per la visualizzazione. Se non utilizzi la grafica in modo efficiente, potresti riscontrare problemi di memoria che possono compromettere le prestazioni della tua app e di altre app sul dispositivo. Segui queste best practice per assicurarti che la tua app funzioni al meglio.

Utilizzare le librerie di caricamento delle immagini

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

Sottocampionare le immagini

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

Sottocampionamento lato client

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

Preferire il ridimensionamento lato server

Se possibile, richiedi le dimensioni esatte dell'immagine di cui hai bisogno direttamente dal server di backend. In questo modo si riduce l'utilizzo della rete e l'ingombro della cache del disco, mentre si riduce la memoria utilizzata evitando il sovraccarico 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 intercettori personalizzati, mentre Glide lo supporta utilizzando caricatori di modelli personalizzati (ad esempio BaseGlideUrlLoader).

Evitare le dimensioni del layout senza vincoli

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

Evita di utilizzare wrapContentSize o di lasciare le dimensioni senza vincoli sui composable che caricano immagini remote. Se queste librerie non riescono a dedurre i limiti di destinazione, tornano a caricare l'immagine originale a grandezza naturale. Ciò può comportare il caricamento di un'immagine molto 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 di pixel esatto, che il caricatore di immagini può quindi utilizzare per richiedere e decodificare la risorsa con le dimensioni corrette.

Fornire risorse alternative per schermi di dimensioni diverse

Se distribuisci immagini con la tua app, valuta la possibilità di fornire risorse di dimensioni diverse per risoluzioni diverse dei dispositivi. In questo modo puoi ridurre le dimensioni del download dell'app sui dispositivi e migliorare le prestazioni, in quanto verrà caricata un'immagine a risoluzione inferiore su un dispositivo a risoluzione inferiore. Per ulteriori informazioni su come fornire bitmap alternative per dispositivi di dimensioni diverse, consulta la documentazione sulle bitmap alternative.

Non applicare direttamente il padding

A volte potrebbe essere necessario aggiungere un padding a un'immagine. Ad esempio, potresti voler circondare l'immagine con un bordo trasparente per il letterboxing. 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 il padding al composable o alla visualizzazione che contiene l'immagine.

Scegliere il formato pixel corretto

Bilancia memoria e qualità scegliendo il formato pixel corretto. Utilizza RGB_565 quando non hai bisogno di trasparenza. Questo formato utilizza la metà della memoria del formato predefinito ARGB_8888.

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

Utilizzare i vettori, se possibile

Per le immagini composte da forme geometriche, una grafica vettoriale è molto più piccola di una bitmap e si adatta perfettamente a qualsiasi densità di visualizzazione. Quando è appropriato, utilizza elementi come ShapeDrawable per rappresentare la grafica.

Rilasciare e riutilizzare le bitmap quando possibile

I file grafici di grandi dimensioni possono occupare molta memoria. Per ridurre il loro impatto, dovresti rilasciare o riutilizzare gli oggetti grafici quando possibile.

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 le esigenze future.

Se gestisci manualmente la grafica, devi rilasciare le bitmap quando hai finito di utilizzarle chiamando Bitmap.recycle ed eliminando 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 dimensioni di download elevate dell'app è la grafica inclusa nel file AAB o APK. Utilizza lo strumento di analisi APK per assicurarti di non includere file immagine più grandi del necessario. Riduci le dimensioni o valuta la possibilità di inserire le immagini su un server e di scaricarle solo quando necessario.

Trovare bitmap ridondanti

Se hai più copie della stessa immagine, la memoria viene sprecata. Puoi utilizzare il profiler di Android Studio per identificare la grafica ridondante. Utilizza l'analizzatore di 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 della visualizzazione 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.

Preferire il passaggio di un Int DrawableRes o di un URL come parametri nel composable anziché Painter

A causa della complessità della gestione delle immagini (ad esempio, scrivere una funzione equals per Bitmaps sarebbe costoso dal punto di vista computazionale), l'API Painter non è contrassegnata esplicitamente come stabile con l'annotazione @Stable. Le classi instabili possono causare 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 disegnabile come parametri al composable, anziché passare un Painter come parametro.

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

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

}