以有效率的方式載入大型點陣圖
透過集合功能整理內容
你可以依據偏好儲存及分類內容。
注意:許多程式庫都會採用最佳做法載入圖片。您可以在應用程式中使用這些程式庫,以最適合的方式載入圖片。建議您使用 Glide 程式庫,載入/顯示圖片最快也最順暢。其他常用的圖片載入程式庫還包括 Square 的 Picasso、Instacart 的 Coil,以及 Facebook 的 Fresco。這些程式庫可簡化與 Android 點陣圖和其他類型圖片相關的大多數複雜工作。
圖片有各種形狀和大小,所占用的空間多半大於一般應用程式使用者介面 (UI)。舉例來說,系統 Gallery 應用程式會顯示使用 Android 裝置相機拍攝的相片,這些相片的解析度通常遠高於裝置的畫面密度。
由於可用的記憶體有限,建議您僅在記憶體中載入解析度較低的版本。此版本的大小應與顯示該版本的 UI 元件相符。解析度較高的圖片不僅沒有顯著優勢,還會占用寶貴的記憶體,且會在執行其他縮放操作時產生額外的效能負擔。
本課程會逐步說明如何在記憶體中載入較小的向下取樣版本,讓您既可以解碼大型點陣圖,又不會超過個別應用程式的記憶體限制。
讀取點陣圖尺寸和類型
BitmapFactory
類別提供可從多種來源建立 Bitmap
的數種解碼方法 (decodeByteArray()
、decodeFile()
、decodeResource()
等)。請根據圖片資料來源選擇最合適的解碼方法。這些方法會試圖為已建構的點陣圖分配記憶體,因此很容易引發 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 元件中。
- 目前裝置的螢幕大小和密度各為多少。
舉例來說,如果 1024 x 768 像素的圖片最終會在 ImageView
中顯示為 128 x 96 像素的縮圖,那就不值得將其載入記憶體中。
如要讓解碼器對圖片進行向下取樣,請將 BitmapFactory.Options
物件的 inSampleSize
設為 true
,在記憶體中載入較小的版本。舉例來說,如果圖片的解析度是 2048 x 1536,且解碼時的 inSampleSize
為 4,就會產生大約 512 x 384 的點陣圖。將此載入記憶體中會用掉 0.75 MB,而非完整圖片所需的 12 MB (假設點陣圖設定為 ARGB_8888
)。以下是根據目標寬度和高度計算範本大小值的方法,該值為二的次方:
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;
}
注意:根據 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);
}
此方法可讓您輕鬆將任意大小的點陣圖載入至顯示 100 x 100 像素縮圖的 ImageView
中,如下列範例程式碼所示:
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 (世界標準時間)。
[null,null,["上次更新時間:2025-07-27 (世界標準時間)。"],[],[],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."]]