高效加载大型位图
使用集合让一切井井有条
根据您的偏好保存内容并对其进行分类。
注意:有几个库遵循了加载图片的最佳实践。您可以在应用中使用这些库,从而以最优化的方式加载图片。我们建议您使用 Glide 库,该库会尽可能快速、顺畅地加载和显示图片。其他常用的图片加载库包括 Square 的 Picasso、Instacart 的 Coil 和 Facebook 的 Fresco。这些库简化了与位图和 Android 上的其他图片类型相关的大多数复杂任务。
图片有各种形状和大小。在很多情况下,它们的大小超过了典型应用界面的要求。例如,系统“图库”应用会显示使用 Android 设备的相机拍摄的照片,这些照片的分辨率通常远高于设备的屏幕密度。
鉴于您使用的内存有限,理想情况下您只希望在内存中加载较低分辨率的版本。分辨率较低的版本应与显示该版本的界面组件的大小相匹配。分辨率更高的图片不会带来任何明显的好处,但仍会占用宝贵的内存,并且会因为额外的动态缩放而产生额外的性能开销。
本节课向您介绍如何通过在内存中加载较小的下采样版本来解码大型位图,从而不超出每个应用的内存限制。
读取位图尺寸和类型
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
或界面组件的尺寸。
- 当前设备的屏幕大小和密度。
例如,如果 1024x768 像素的图片最终会在 ImageView
中显示为 128x96 像素缩略图,则不值得将其加载到内存中。
如需让解码器对图片进行下采样,以将较小版本加载到内存中,请在 BitmapFactory.Options
对象中将 inSampleSize
设置为 true
。例如,分辨率为 2048x1536 且以 4 作为 inSampleSize
进行解码的图片会生成大约 512x384 的位图。将此图片加载到内存中需使用 0.75MB,而不是完整图片所需的 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;
}
注意:根据 inSampleSize
文档,计算 2 的幂的原因是解码器使用的最终值将向下舍入为最接近的 2 的幂。
如需使用此方法,请先将 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);
}
采用此方法,您可以轻松地将任意大尺寸的位图加载到显示 100x100 像素缩略图的 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 和/或其关联公司的注册商标。
最后更新时间 (UTC):2025-07-27。
[null,null,["最后更新时间 (UTC):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."]]