Caricamento efficiente di bitmap di grandi dimensioni

Nota: esistono diverse librerie che seguono best practice per il caricamento delle immagini. Puoi usare queste librerie nella tua app per per caricare le immagini nel modo più ottimizzato. Consigliamo Scorrimento che carica e visualizza le immagini nel modo più rapido e semplice possibile. Altre librerie di caricamento delle immagini popolari includono Picasso di Square, Coil di Instacart e Affresco da Facebook. Queste librerie semplificano la maggior parte delle attività complesse associate con bitmap e altri tipi di immagini su Android.

Le immagini sono disponibili in tutte le forme e dimensioni. In molti casi sono più grandi di quanto richiesto per una l'interfaccia utente di un'applicazione. Ad esempio, l'applicazione Galleria di sistema mostra le foto scattate usando le fotocamere dei tuoi dispositivi Android, che in genere hanno una risoluzione molto più alta rispetto allo schermo densità del dispositivo.

Dato che stai lavorando con memoria limitata, idealmente dovresti caricare solo una risoluzione più bassa in memoria. La versione con risoluzione più bassa deve corrispondere alle dimensioni del componente dell'interfaccia utente che la visualizza. Un'immagine con una risoluzione più alta non offre alcun vantaggio visibile, ma richiede comunque di memoria preziosa e comporta un ulteriore overhead delle prestazioni dovuto a e la scalabilità delle applicazioni.

Questa lezione illustra come decodificare bitmap di grandi dimensioni senza superare il limite per applicazione limite di memoria caricando una versione sottocampionata più piccola in memoria.

Leggi dimensioni e tipo di bitmap

La classe BitmapFactory fornisce diversi metodi di decodifica (decodeByteArray(), decodeFile(), decodeResource() e così via) per creare un Bitmap da varie fonti. Scegli il metodo di decodifica più appropriato in base all'origine dati immagine. Questi metodi tentano di allocare memoria per la bitmap creata e quindi può facilmente generare un OutOfMemory . Ogni tipo di metodo di decodifica ha firme aggiuntive che ti consentono di specificare la decodifica opzioni tramite il corso BitmapFactory.Options. 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 e il tipo di dati dell'immagine prima della costruzione (e dell'allocazione della memoria) 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 java.lang.OutOfMemory eccezioni, controlla le dimensioni di una bitmap prima decodificarlo, a meno che tu non ti fidi completamente della fonte per fornire dati delle immagini di dimensioni prevedibili che si inserisce comodamente nella memoria disponibile.

Carica una versione scalata in memoria

Ora che le dimensioni dell'immagine sono note, possono essere utilizzate per decidere se l'immagine intera deve in memoria o se è necessario caricare una versione sottocampionata. Di seguito sono riportati alcuni fattori da considerare considera:

  • Utilizzo stimato della memoria per il caricamento dell'intera immagine in memoria.
  • Quantità di memoria che intendi impegnare a caricare questa immagine in base a qualsiasi altra memoria i requisiti della tua applicazione.
  • Dimensioni del componente ImageView o UI target a cui l'immagine in cui eseguire il caricamento.
  • Dimensioni e densità dello schermo del dispositivo corrente.

Ad esempio, non vale la pena caricare in memoria un'immagine di 1024 x 768 pixel se alla fine visualizzato 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 che viene decodificato con un inSampleSize di 4, produce una bitmap di circa 512 x 384. Il caricamento in memoria richiede 0,75 MB anziché 12 MB per (supponendo una configurazione bitmap di ARGB_8888). Ecco un metodo per calcolare un valore di dimensione del campione che abbia una potenza di 2 in base alla larghezza e al valore altezza:

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 al due, come indicato nella documentazione inSampleSize.

Per utilizzare questo metodo, prima decodifica con inJustDecodeBounds impostato su true, passa le opzioni quindi la decodificano nuovamente 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 arbitrariamente grandi in un ImageView che visualizza una miniatura di 100 x 100 pixel, come mostrato nell'esempio seguente codice:

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 metodo BitmapFactory.decode* appropriato in base alle esigenze.