การโหลดบิตแมปขนาดใหญ่อย่างมีประสิทธิภาพ

หมายเหตุ: มีไลบรารีหลายแห่งที่ตามมา แนวทางปฏิบัติที่ดีที่สุดในการโหลดรูปภาพ คุณใช้ไลบรารีเหล่านี้ในแอปเพื่อทำสิ่งต่อไปนี้ได้ โหลดรูปภาพในลักษณะที่เหมาะสมที่สุด เราขอแนะนำให้ แบบเลื่อนผ่าน ซึ่งจะโหลดและแสดงภาพอย่างรวดเร็วและราบรื่นที่สุดเท่าที่จะเป็นไปได้ ไลบรารีการโหลดรูปภาพยอดนิยมอื่นๆ ได้แก่ Picasso จาก Square, Cil จาก Instacart และ เฟรสโก จาก Facebook ไลบรารีเหล่านี้ช่วยลดความซับซ้อนของงานที่ซับซ้อนส่วนใหญ่ที่เกี่ยวข้อง กับบิตแมปและรูปภาพประเภทอื่นๆ บน Android

มีรูปภาพได้หลายขนาดและรูปทรง ในหลายกรณี มักมีปริมาณมากกว่าที่มักต้องใช้ อินเทอร์เฟซผู้ใช้ของแอปพลิเคชัน (UI) ตัวอย่างเช่น แอปพลิเคชันแกลเลอรีของระบบจะแสดงภาพที่ถ่าย โดยใช้กล้องของอุปกรณ์ Android ซึ่งโดยทั่วไปจะมีความละเอียดสูงกว่าหน้าจอมาก ความหนาแน่นของอุปกรณ์

เนื่องจากคุณทำงานกับหน่วยความจำที่จำกัด คุณควรโหลดแค่ความละเอียดที่ต่ำลงเท่านั้น ในหน่วยความจำ เวอร์ชันความละเอียดต่ำกว่าควรตรงกับขนาดของคอมโพเนนต์ UI ที่ แสดงสิ่งนั้น รูปภาพที่มีความละเอียดสูงกว่าจะไม่มีประโยชน์ใดๆ แต่ยังคงถ่าย สร้างหน่วยความจำที่มีค่า และทำให้มีค่าใช้จ่ายในการปฏิบัติงานเพิ่มขึ้นเนื่องจากเพิ่มขึ้นอย่างต่อเนื่อง การปรับขนาด

บทเรียนนี้จะแนะนำวิธีถอดรหัสบิตแมปขนาดใหญ่โดยไม่มีค่าใช้จ่ายเกินต่อแอปพลิเคชัน ขีดจำกัดหน่วยความจำโดยการโหลดเวอร์ชันตัวอย่างย่อยที่เล็กลงในหน่วยความจำ

อ่านมิติข้อมูลและประเภทบิตแมป

คลาส BitmapFactory มีวิธีถอดรหัสหลายวิธี (decodeByteArray(), decodeFile(), decodeResource() ฯลฯ) สำหรับการสร้าง Bitmap จากแหล่งที่มาต่างๆ เลือก วิธีการถอดรหัสที่เหมาะสมที่สุดตามแหล่งข้อมูลของรูปภาพ วิธีการเหล่านี้จะพยายาม จัดสรรหน่วยความจำสำหรับบิตแมปที่สร้างขึ้น จึงอาจทำให้เกิด OutOfMemory ได้อย่างง่ายดาย ข้อยกเว้น วิธีการถอดรหัสแต่ละประเภทมีลายเซ็นเพิ่มเติมที่ช่วยให้คุณระบุการถอดรหัสได้ ตัวเลือกผ่านชั้นเรียน BitmapFactory.Options การตั้งค่าพร็อพเพอร์ตี้ inJustDecodeBounds เป็น true ขณะถอดรหัส หลีกเลี่ยงการจัดสรรหน่วยความจำ โดยแสดงผล null สำหรับออบเจ็กต์บิตแมป แต่ตั้งค่า outWidth, outHeight และ outMimeType เทคนิคนี้จะช่วยให้คุณอ่าน ขนาดและประเภทของข้อมูลรูปภาพก่อนการสร้าง (และการจัดสรรหน่วยความจำ) บิตแมป

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;

เพื่อหลีกเลี่ยงข้อยกเว้น java.lang.OutOfMemory โปรดตรวจสอบขนาดของบิตแมปก่อน ถอดรหัสไฟล์ เว้นแต่คุณจะไว้ใจให้แหล่งที่มาให้ข้อมูลภาพที่มีขนาดตามที่คาดหมายได้ ที่สามารถพอดีกับหน่วยความจำที่มีอยู่ได้

โหลดเวอร์ชันที่ลดขนาดลงในหน่วยความจำ

เมื่อทราบขนาดรูปภาพแล้ว คุณสามารถนําไปใช้ตัดสินใจได้ว่าควรใช้รูปภาพขนาดเต็มหรือไม่ โหลดลงในหน่วยความจำหรือหากควรโหลดเวอร์ชันตัวอย่างย่อยแทน ปัจจัยบางส่วนที่จะมีดังนี้ พิจารณา

  • การใช้งานหน่วยความจำโดยประมาณเมื่อโหลดรูปภาพขนาดเต็มในหน่วยความจำ
  • จำนวนหน่วยความจำที่คุณยินดีจ่ายเพื่อโหลดรูปภาพนี้เมื่อพิจารณาจากหน่วยความจำอื่นๆ ข้อกำหนดของการสมัคร
  • ขนาดคอมโพเนนต์ ImageView หรือคอมโพเนนต์ UI เป้าหมายที่รูปภาพ จะมีการโหลดเข้าไป
  • ขนาดและความหนาแน่นของหน้าจออุปกรณ์ปัจจุบัน

ตัวอย่างเช่น การโหลดรูปภาพขนาด 1024x768 พิกเซลลงในหน่วยความจำก็ไม่คุ้มถ้าสุดท้ายแล้วภาพนั้นจะ ที่แสดงในภาพขนาดย่อ 128x96 พิกเซลใน ImageView

หากต้องการบอกให้ตัวถอดรหัสสุ่มตัวอย่างรูปภาพย่อย ให้โหลดเวอร์ชันขนาดเล็กลงในหน่วยความจำ ตั้งค่า inSampleSize เป็น true ในออบเจ็กต์ BitmapFactory.Options ตัวอย่างเช่น รูปภาพที่มีความละเอียด 2048x1536 ถูกถอดรหัสด้วย inSampleSize ของ 4 จะสร้าง ขนาดประมาณ 512x384 การโหลดนี้ลงในหน่วยความจำจะใช้หน่วยความจำเต็ม 0.75 MB แทนที่จะเป็น 12MB รูปภาพ (สมมติว่ามีการกำหนดค่าบิตแมปของ ARGB_8888) นี่คือ วิธีคำนวณค่าขนาดตัวอย่างที่ยกกำลัง 2 ตามความกว้างเป้าหมายและ ส่วนสูง:

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;
}

หมายเหตุ: ระบบจะคํานวณเลข 2 ค่าเนื่องจากตัวถอดรหัสใช้ ค่าสุดท้ายโดยการปัดเศษลงยกกำลัง 2 ที่ใกล้เคียงที่สุดตามเอกสารประกอบ inSampleSize

หากต้องการใช้วิธีนี้ ให้ถอดรหัสด้วยการตั้งค่า inJustDecodeBounds เป็น true ก่อน แล้วจึงส่งต่อตัวเลือก จากนั้นถอดรหัสอีกครั้งโดยใช้ค่า inSampleSize ใหม่และตั้งค่า inJustDecodeBounds เป็น 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);
}

วิธีนี้ช่วยให้ง่ายต่อการโหลดบิตแมปขนาดใหญ่ที่กำหนดเองลงใน ImageView ซึ่งแสดงภาพขนาดย่อขนาด 100x100 พิกเซล ดังที่แสดงในตัวอย่างต่อไปนี้ รหัส:

Kotlin

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

Java

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

คุณสามารถทำตามขั้นตอนที่คล้ายกันนี้เพื่อถอดรหัสบิตแมปจากแหล่งที่มาอื่นๆ โดยแทนที่ เมธอด BitmapFactory.decode* ที่เหมาะสมได้ตามต้องการ