Nota: esistono diverse librerie che seguono le best practice per il caricamento delle immagini. Puoi usare queste librerie nella tua app per caricare le immagini nel modo più ottimizzato. Ti consigliamo la libreria Glide, che carica e visualizza le immagini nel modo più rapido e fluido possibile. Altre librerie di caricamento immagini popolari includono Picasso da Square, Coil di Instacart e Fresco da Facebook. Queste librerie semplificano la maggior parte delle attività complesse associate ai bitmap e ad altri tipi di immagini su Android.
Le immagini sono disponibili in tutte le forme e dimensioni. In molti casi, sono più grandi del necessario per una tipica interfaccia utente (interfaccia utente). Ad esempio, l'applicazione Galleria di sistema mostra le foto scattate con la fotocamera dei dispositivi Android, che in genere hanno una risoluzione molto più elevata rispetto alla densità dello schermo del dispositivo.
Dato che stai lavorando con memoria limitata, idealmente vuoi caricare solo una versione a risoluzione più bassa in memoria. La versione con risoluzione inferiore deve corrispondere alle dimensioni del componente dell'interfaccia utente che la mostra. Un'immagine con una risoluzione più alta non offre alcun vantaggio visibile, ma occupa comunque memoria preziosa e comporta un ulteriore sovraccarico delle prestazioni dovuto a una scalabilità aggiuntiva immediata.
Questa lezione illustra la decodifica di bitmap di grandi dimensioni senza superare il limite di memoria per applicazione caricando una versione sottocampionata più piccola in memoria.
Lettura tipo e dimensioni bitmap
La classe BitmapFactory
offre diversi metodi di decodifica (decodeByteArray()
, decodeFile()
, decodeResource()
e così via) per creare un Bitmap
da varie origini. Scegli il metodo di decodifica più appropriato in base all'origine dati dell'immagine. Questi metodi tentano di allocare memoria per la bitmap creata e possono pertanto facilmente comportare un'eccezione OutOfMemory
. Ogni tipo di metodo di decodifica ha firme aggiuntive che ti consentono di specificare le opzioni di decodifica tramite la classe BitmapFactory.Options
. L'impostazione della proprietà inJustDecodeBounds
su true
durante la decodifica evita l'allocazione della memoria, restituendo null
per l'oggetto bitmap, ma impostando outWidth
, outHeight
e outMimeType
. Questa tecnica consente di leggere le dimensioni e il tipo dei dati dell'immagine prima della creazione (e dell'allocazione della memoria) della bitmap.
Kotlin
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeResource(resources, R.id.myimage, options) val imageHeight: Int = options.outHeight val imageWidth: Int = options.outWidth val imageType: String = options.outMimeType
Java
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;
Per evitare eccezioni java.lang.OutOfMemory
, controlla le dimensioni di una bitmap prima di decodificarla, a meno che tu non ti fidi assolutamente dell'origine di dati immagine di dimensioni prevedibili che rientrino comodamente nella memoria disponibile.
Carica una versione scalata in memoria
Ora che sono note, le dimensioni dell'immagine possono essere utilizzate per decidere se caricare in memoria l'immagine completa o se caricare una versione sottocampionata. Ecco alcuni fattori da considerare:
- Utilizzo stimato della memoria per il caricamento dell'immagine intera in memoria.
- Quantità di memoria che intendi impegnare per il caricamento di questa immagine in base a qualsiasi altro requisito di memoria della tua applicazione.
- Dimensioni del componente di interfaccia utente o
ImageView
di destinazione in cui deve essere caricata l'immagine. - Dimensioni e densità dello schermo del dispositivo attuale.
Ad esempio, non vale la pena caricare un'immagine da 1024 x 768 pixel in memoria se alla fine verrà visualizzata in una miniatura di 128 x 96 pixel in un ImageView
.
Per indicare al decoder di sottocampionare l'immagine, caricando una versione più piccola in memoria, imposta inSampleSize
su true
nell'oggetto BitmapFactory.Options
. Ad esempio, un'immagine con risoluzione 2048 x 1536 decodificata con un valore inSampleSize
di 4 produce una bitmap di circa 512 x 384. Il caricamento di questo elemento in memoria utilizza 0,75 MB anziché 12 MB per l'immagine completa (presupponendo una configurazione bitmap ARGB_8888
). Ecco un metodo per calcolare un valore di dimensione campione che corrisponde alla potenza di due in base alla larghezza e all'altezza target:
Kotlin
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { // Raw height and width of image val (height: Int, width: Int) = options.run { outHeight to outWidth } var inSampleSize = 1 if (height > reqHeight || width > reqWidth) { val halfHeight: Int = height / 2 val halfWidth: Int = width / 2 // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { inSampleSize *= 2 } } return inSampleSize }
Java
public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
Nota: viene calcolata una potenza di due valori perché il decoder utilizza un valore finale arrotondando per difetto al valore più vicino di due, come indicato nella documentazione su inSampleSize
.
Per utilizzare questo metodo, devi prima decodificare con inJustDecodeBounds
impostato su true
, trasmettere le opzioni, quindi decodificare di nuovo utilizzando il nuovo valore inSampleSize
e inJustDecodeBounds
impostato su false
:
Kotlin
fun decodeSampledBitmapFromResource( res: Resources, resId: Int, reqWidth: Int, reqHeight: Int ): Bitmap { // First decode with inJustDecodeBounds=true to check dimensions return BitmapFactory.Options().run { inJustDecodeBounds = true BitmapFactory.decodeResource(res, resId, this) // Calculate inSampleSize inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight) // Decode bitmap with inSampleSize set inJustDecodeBounds = false BitmapFactory.decodeResource(res, resId, this) } }
Java
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }
Questo metodo semplifica il caricamento di una bitmap di dimensioni arbitrarie in un ImageView
che visualizza una miniatura di 100 x 100 pixel, come mostrato nel codice di esempio riportato di seguito:
Kotlin
imageView.setImageBitmap( decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100) )
Java
imageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
Puoi seguire una procedura simile per decodificare le bitmap da altre origini, sostituendo il metodo BitmapFactory.decode*
appropriato in base alle tue esigenze.