注意:对于大多数情况下,我们建议您使用Glide库来获取,解码和显示应用程序中的位图。 Glide将处理与Android上的位图和其他图像相关的这些和其他任务的大部分复杂性抽象化。 有关使用和下载Glide的信息,请访问GitHub上的Glide存储库。
图像有各种形状和大小。 在许多情况下,它们大于典型应用程序用户界面(UI)所需的大小。 例如,系统图库应用程序会显示使用Android设备的相机拍摄的照片,这些照片通常比设备的屏幕密度高得多。
鉴于您正在使用有限的内存,理想情况下,您只需要在内存中加载较低的分辨率版本。 较低的分辨率版本应该与显示它的UI组件的大小相匹配。 具有较高分辨率的图像不会提供任何可见的好处,但仍然占用宝贵的内存并由于额外的即时缩放而导致额外的性能开销。
本课将向您介绍通过在内存中加载较小的子采样版本来解码大型位图,而不会超出每个应用程序的内存限制。
读取位图尺寸和类型
BitmapFactory类提供了多种解码方法(decodeByteArray(),decodeFile(),decodeResource()等),用于从各种来源创建位图。 根据您的图像数据源选择最合适的解码方法。 这些方法试图为构造的位图分配内存,因此很容易导致OutOfMemory异常。 每种类型的解码方法都有额外的签名,可让您通过BitmapFactory.Options类指定解码选项。 在解码过程中将inJustDecodeBounds属性设置为true可以避免内存分配,对于位图对象返回null,但是设置了Width,outHeight和outMimeType。 这种技术允许您在构建位图之前读取图像数据的尺寸和类型(以及内存分配)。
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组件的尺寸。
- 当前设备的屏幕大小和密度。
例如,如果最终将在ImageView中以128x96像素的缩略图显示,则将1024x768像素的图像加载到内存中是不值得的。
为了告诉解码器对图像进行二次采样,将较小的版本加载到内存中,请在BitmapFactory.Options对象中将inSampleSize设置为true。 例如,分辨率为2048x1536且用inSampleSize为4解码的图像会生成大约512x384的位图。 加载到内存使用0.75MB,而不是12MB的完整图像(假设位图配置ARGB_8888)。 以下是根据目标宽度和高度计算两个幂的样本大小值的方法
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再次解码:
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中,如以下示例代码所示:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
您可以按照类似的过程来解码来自其他来源的位图,方法是根据需要替换相应的BitmapFactory.decode *方法。