การโหลดบิตแมปขนาดใหญ่อย่างมีประสิทธิภาพ
จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน
บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ
หมายเหตุ: มีไลบรารีหลายแห่งที่ตามมา
แนวทางปฏิบัติที่ดีที่สุดในการโหลดรูปภาพ คุณใช้ไลบรารีเหล่านี้ในแอปเพื่อทำสิ่งต่อไปนี้ได้
โหลดรูปภาพในลักษณะที่เหมาะสมที่สุด เราขอแนะนำให้
แบบเลื่อนผ่าน
ซึ่งจะโหลดและแสดงภาพอย่างรวดเร็วและราบรื่นที่สุดเท่าที่จะเป็นไปได้
ไลบรารีการโหลดรูปภาพยอดนิยมอื่นๆ ได้แก่ 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*
ที่เหมาะสมได้ตามต้องการ
ตัวอย่างเนื้อหาและโค้ดในหน้าเว็บนี้ขึ้นอยู่กับใบอนุญาตที่อธิบายไว้ในใบอนุญาตการใช้เนื้อหา Java และ OpenJDK เป็นเครื่องหมายการค้าหรือเครื่องหมายการค้าจดทะเบียนของ Oracle และ/หรือบริษัทในเครือ
อัปเดตล่าสุด 2025-07-27 UTC
[[["เข้าใจง่าย","easyToUnderstand","thumb-up"],["แก้ปัญหาของฉันได้","solvedMyProblem","thumb-up"],["อื่นๆ","otherUp","thumb-up"]],[["ไม่มีข้อมูลที่ฉันต้องการ","missingTheInformationINeed","thumb-down"],["ซับซ้อนเกินไป/มีหลายขั้นตอนมากเกินไป","tooComplicatedTooManySteps","thumb-down"],["ล้าสมัย","outOfDate","thumb-down"],["ปัญหาเกี่ยวกับการแปล","translationIssue","thumb-down"],["ตัวอย่าง/ปัญหาเกี่ยวกับโค้ด","samplesCodeIssue","thumb-down"],["อื่นๆ","otherDown","thumb-down"]],["อัปเดตล่าสุด 2025-07-27 UTC"],[],[],null,["# Loading Large Bitmaps Efficiently\n\n**Note:** There are several libraries that follow\nbest practices for loading images. You can use these libraries in your app to\nload images in the most optimized manner. We recommend the\n[Glide](https://github.com/bumptech/glide)\nlibrary, which loads and displays images as quickly and smoothly as possible.\nOther popular image loading libraries include [Picasso](http://square.github.io/picasso/) from Square, [Coil](https://github.com/coil-kt/coil) from Instacart, and\n[Fresco](https://github.com/facebook/fresco)\nfrom Facebook. These libraries simplify most of the complex tasks associated\nwith bitmaps and other types of images on Android.\n\nImages come in all shapes and sizes. In many cases they are larger than required for a typical\napplication user interface (UI). For example, the system Gallery application displays photos taken\nusing your Android devices's camera which are typically much higher resolution than the screen\ndensity of your device.\n\nGiven that you are working with limited memory, ideally you only want to load a lower resolution\nversion in memory. The lower resolution version should match the size of the UI component that\ndisplays it. An image with a higher resolution does not provide any visible benefit, but still takes\nup precious memory and incurs additional performance overhead due to additional on the fly\nscaling.\n\nThis lesson walks you through decoding large bitmaps without exceeding the per application\nmemory limit by loading a smaller subsampled version in memory.\n\nRead Bitmap Dimensions and Type\n-------------------------------\n\nThe [BitmapFactory](/reference/android/graphics/BitmapFactory) class provides several decoding methods ([decodeByteArray()](/reference/android/graphics/BitmapFactory#decodeByteArray(byte[], int, int, android.graphics.BitmapFactory.Options)), [decodeFile()](/reference/android/graphics/BitmapFactory#decodeFile(java.lang.String, android.graphics.BitmapFactory.Options)), [decodeResource()](/reference/android/graphics/BitmapFactory#decodeResource(android.content.res.Resources, int, android.graphics.BitmapFactory.Options)), etc.) for creating a [Bitmap](/reference/android/graphics/Bitmap) from various sources. Choose\nthe most appropriate decode method based on your image data source. These methods attempt to\nallocate memory for the constructed bitmap and therefore can easily result in an `OutOfMemory`\nexception. Each type of decode method has additional signatures that let you specify decoding\noptions via the [BitmapFactory.Options](/reference/android/graphics/BitmapFactory.Options) class. Setting the [inJustDecodeBounds](/reference/android/graphics/BitmapFactory.Options#inJustDecodeBounds) property to `true` while decoding\navoids memory allocation, returning `null` for the bitmap object but setting [outWidth](/reference/android/graphics/BitmapFactory.Options#outWidth), [outHeight](/reference/android/graphics/BitmapFactory.Options#outHeight) and [outMimeType](/reference/android/graphics/BitmapFactory.Options#outMimeType). This technique allows you to read the\ndimensions and type of the image data prior to construction (and memory allocation) of the\nbitmap. \n\n### Kotlin\n\n```kotlin\nval options = BitmapFactory.Options().apply {\n inJustDecodeBounds = true\n}\nBitmapFactory.decodeResource(resources, R.id.myimage, options)\nval imageHeight: Int = options.outHeight\nval imageWidth: Int = options.outWidth\nval imageType: String = options.outMimeType\n```\n\n### Java\n\n```java\nBitmapFactory.Options options = new BitmapFactory.Options();\noptions.inJustDecodeBounds = true;\nBitmapFactory.decodeResource(getResources(), R.id.myimage, options);\nint imageHeight = options.outHeight;\nint imageWidth = options.outWidth;\nString imageType = options.outMimeType;\n```\n\nTo avoid `java.lang.OutOfMemory` exceptions, check the dimensions of a bitmap before\ndecoding it, unless you absolutely trust the source to provide you with predictably sized image data\nthat comfortably fits within the available memory.\n\nLoad a Scaled Down Version into Memory\n--------------------------------------\n\nNow that the image dimensions are known, they can be used to decide if the full image should be\nloaded into memory or if a subsampled version should be loaded instead. Here are some factors to\nconsider:\n\n- Estimated memory usage of loading the full image in memory.\n- Amount of memory you are willing to commit to loading this image given any other memory requirements of your application.\n- Dimensions of the target [ImageView](/reference/android/widget/ImageView) or UI component that the image is to be loaded into.\n- Screen size and density of the current device.\n\nFor example, it's not worth loading a 1024x768 pixel image into memory if it will eventually be\ndisplayed in a 128x96 pixel thumbnail in an [ImageView](/reference/android/widget/ImageView).\n\nTo tell the decoder to subsample the image, loading a smaller version into memory, set [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) to `true` in your [BitmapFactory.Options](/reference/android/graphics/BitmapFactory.Options) object. For example, an image with resolution 2048x1536 that\nis decoded with an [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) of 4 produces a\nbitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full\nimage (assuming a bitmap configuration of [ARGB_8888](/reference/android/graphics/Bitmap.Config)). Here's\na method to calculate a sample size value that is a power of two based on a target width and\nheight: \n\n### Kotlin\n\n```kotlin\nfun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {\n // Raw height and width of image\n val (height: Int, width: Int) = options.run { outHeight to outWidth }\n var inSampleSize = 1\n\n if (height \u003e reqHeight || width \u003e reqWidth) {\n\n val halfHeight: Int = height / 2\n val halfWidth: Int = width / 2\n\n // Calculate the largest inSampleSize value that is a power of 2 and keeps both\n // height and width larger than the requested height and width.\n while (halfHeight / inSampleSize \u003e= reqHeight && halfWidth / inSampleSize \u003e= reqWidth) {\n inSampleSize *= 2\n }\n }\n\n return inSampleSize\n}\n```\n\n### Java\n\n```java\npublic static int calculateInSampleSize(\n BitmapFactory.Options options, int reqWidth, int reqHeight) {\n // Raw height and width of image\n final int height = options.outHeight;\n final int width = options.outWidth;\n int inSampleSize = 1;\n\n if (height \u003e reqHeight || width \u003e reqWidth) {\n\n final int halfHeight = height / 2;\n final int halfWidth = width / 2;\n\n // Calculate the largest inSampleSize value that is a power of 2 and keeps both\n // height and width larger than the requested height and width.\n while ((halfHeight / inSampleSize) \u003e= reqHeight\n && (halfWidth / inSampleSize) \u003e= reqWidth) {\n inSampleSize *= 2;\n }\n }\n\n return inSampleSize;\n}\n```\n\n**Note:** A power of two value is calculated because the decoder uses\na final value by rounding down to the nearest power of two, as per the [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) documentation.\n\nTo use this method, first decode with [inJustDecodeBounds](/reference/android/graphics/BitmapFactory.Options#inJustDecodeBounds) set to `true`, pass the options\nthrough and then decode again using the new [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) value and [inJustDecodeBounds](/reference/android/graphics/BitmapFactory.Options#inJustDecodeBounds) set to `false`: \n\n### Kotlin\n\n```kotlin\nfun decodeSampledBitmapFromResource(\n res: Resources,\n resId: Int,\n reqWidth: Int,\n reqHeight: Int\n): Bitmap {\n // First decode with inJustDecodeBounds=true to check dimensions\n return BitmapFactory.Options().run {\n inJustDecodeBounds = true\n BitmapFactory.decodeResource(res, resId, this)\n\n // Calculate inSampleSize\n inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)\n\n // Decode bitmap with inSampleSize set\n inJustDecodeBounds = false\n\n BitmapFactory.decodeResource(res, resId, this)\n }\n}\n```\n\n### Java\n\n```java\npublic static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,\n int reqWidth, int reqHeight) {\n\n // First decode with inJustDecodeBounds=true to check dimensions\n final BitmapFactory.Options options = new BitmapFactory.Options();\n options.inJustDecodeBounds = true;\n BitmapFactory.decodeResource(res, resId, options);\n\n // Calculate inSampleSize\n options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);\n\n // Decode bitmap with inSampleSize set\n options.inJustDecodeBounds = false;\n return BitmapFactory.decodeResource(res, resId, options);\n}\n```\n\nThis method makes it easy to load a bitmap of arbitrarily large size into an [ImageView](/reference/android/widget/ImageView) that displays a 100x100 pixel thumbnail, as shown in the following example\ncode: \n\n### Kotlin\n\n```kotlin\nimageView.setImageBitmap(\n decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)\n)\n```\n\n### Java\n\n```java\nimageView.setImageBitmap(\n decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));\n```\n\nYou can follow a similar process to decode bitmaps from other sources, by substituting the\nappropriate [BitmapFactory.decode*](/reference/android/graphics/BitmapFactory#decodeByteArray(byte[], int, int, android.graphics.BitmapFactory.Options)) method as needed."]]