Memuat Bitmap Besar Secara Efisien

Catatan: Ada beberapa library yang mengikuti praktik terbaik untuk memuat gambar. Anda dapat menggunakan library tersebut di aplikasi untuk memuat gambar dengan cara yang paling optimal. Kami merekomendasikan library Glide, yang memuat dan menampilkan gambar secepat dan selancar mungkin. Library pemuatan gambar populer lainnya mencakup Picasso dari Square, Coil dari Instacart, dan Fresco dari Facebook. Semua library ini menyederhanakan sebagian besar tugas kompleks yang terkait dengan bitmap dan jenis gambar lainnya di Android.

Gambar tersedia dalam berbagai bentuk dan ukuran. Dalam banyak kasus, gambar lebih besar daripada yang diperlukan untuk antarmuka pengguna (UI) aplikasi biasa. Misalnya, aplikasi Galeri sistem menampilkan foto yang diambil menggunakan kamera perangkat Android yang biasanya beresolusi lebih tinggi daripada kepadatan layar perangkat.

Mengingat bahwa Anda bekerja dengan memori terbatas, idealnya Anda hanya ingin memuat versi resolusi yang lebih rendah dalam memori. Versi resolusi lebih rendah harus cocok dengan ukuran komponen UI yang menampilkannya. Gambar dengan resolusi lebih tinggi tidak memberikan manfaat yang mencolok, tetapi tetap menghabiskan memori yang berharga dan menimbulkan overhead performa tambahan karena penskalaan tambahan saat proses berjalan.

Pelajaran ini memandu Anda mendekode bitmap besar tanpa melebihi batas memori per aplikasi dengan memuat versi subsampel yang lebih kecil dalam memori.

Membaca Dimensi dan Jenis Bitmap

Class BitmapFactory menyediakan beberapa metode dekode (decodeByteArray(), decodeFile(), decodeResource(), dll.) untuk membuat Bitmap dari berbagai sumber. Pilih metode dekode yang paling sesuai berdasarkan sumber data gambar. Metode ini berupaya mengalokasikan memori untuk bitmap yang dikonstruksi dan, oleh karena itu, dapat dengan mudah menghasilkan pengecualian OutOfMemory. Tiap jenis metode dekode memiliki tanda tangan tambahan yang memungkinkan Anda menentukan opsi dekode melalui class BitmapFactory.Options. Menetapkan properti inJustDecodeBounds ke nilai true saat mendekode akan mencegah alokasi memori, menampilkan null untuk objek bitmap, tetapi menetapkan outWidth, outHeight, dan outMimeType. Teknik ini memungkinkan Anda membaca dimensi dan jenis data gambar sebelum konstruksi (dan alokasi memori) 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;

Untuk mencegah pengecualian java.lang.OutOfMemory, periksa dimensi bitmap sebelum mendekodenya, kecuali Anda benar-benar memercayai sumber untuk memberi Anda data gambar dengan ukuran yang dapat diprediksi, yang sangat sesuai dengan memori yang tersedia.

Memuat Versi yang Diperkecil ke Memori

Setelah dimensi gambar diketahui, dimensi dapat digunakan untuk menentukan apakah gambar penuh harus dimuat ke memori atau apakah versi subsampel harus dimuat sebagai gantinya. Berikut ini beberapa faktor yang perlu dipertimbangkan:

  • Perkiraan penggunaan memori untuk memuat gambar penuh dalam memori.
  • Jumlah memori yang ingin Anda alokasikan untuk memuat gambar ini dengan mempertimbangkan kebutuhan memori lainnya pada aplikasi.
  • Dimensi ImageView target atau komponen UI yang menerima gambar yang dimuat.
  • Ukuran dan kepadatan layar perangkat saat ini.

Misalnya, gambar piksel 1024x768 tidak layak dimuat ke memori jika pada akhirnya akan ditampilkan dalam thumbnail piksel 128x96 di ImageView.

Untuk memberi tahu decoder agar membuat subsampel gambar, memuat versi yang lebih kecil ke memori, tetapkan inSampleSize ke true di objek BitmapFactory.Options. Misalnya, gambar dengan resolusi 2048x1536 yang didekode dengan inSampleSize dari 4 menghasilkan bitmap berukuran kira-kira 512x384. Pemuatan versi ini ke memori menggunakan 0,75 MB, bukan 12 MB, untuk gambar penuh (dengan asumsi konfigurasi bitmap ARGB_8888). Berikut ini metode untuk menghitung contoh nilai ukuran yang merupakan pangkat dua berdasarkan lebar dan tinggi 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;
}

Catatan: Nilai pangkat dua dihitung karena decoder menggunakan nilai akhir dengan membulatkan ke bawah mendekati pangkat dua terdekat, menurut dokumentasi inSampleSize.

Untuk menggunakan metode ini, terlebih dulu lakukan dekode dengan inJustDecodeBounds yang ditetapkan ke true, teruskan opsi, lalu dekode lagi menggunakan nilai inSampleSize yang baru dan inJustDecodeBounds yang ditetapkan ke 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);
}

Metode ini memudahkan pemuatan bitmap berukuran besar bebas ke ImageView yang menampilkan thumbnail piksel 100x100, seperti ditunjukkan dalam contoh kode berikut:

Kotlin

imageView.setImageBitmap(
        decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)
)

Java

imageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

Anda dapat mengikuti proses serupa untuk mendekode bitmap dari sumber lain, dengan menggantikan metode BitmapFactory.decode* yang sesuai menurut kebutuhan.