图片可能包含各种形状和尺寸,在许多情况下,他们可能会超出UI组件的尺寸要求,比如,系统的相册应用展示设备相机拍的照片,这些照片像素可能会超出设备的屏幕尺寸。由于应用的内存限制,你可能只想在内存中持有分辨率更低的版本,这个版本应该跟UI组件上展示的尺寸相匹配。高分辨率不一定提供明显的好处,但是仍然占用着宝贵的内存,并可能因为附加的扩展产生额外的性能开销。这一节将学习,在不超过单个应用内存限的情况下,通过加载小分辨率的图片样品的方式解码大容量位图。
读取位图的规模和类型
位图工厂类提供了一些解码方法(decodeByteArray(),decodeFile(),decodeResource()等),用于通过不同的资源创建一个位图,使用时基于你的图片数据资源,应选择最合适的解码方法。这些方法试图为构造好的位图分配内存,所以会很容易导致内存溢出问题。每个类型的解码方法都有附加的签名,使你可以通过BitmapFactory.Option类指定解码的选项。设置inJustDecodeBounds属性为true,解码时可以避免内存分配,返回null给位图对象,并能够设置outWidth和outHeight和outMimeType。这种技术允许你在位图构造或分配内存之前读取图片数据的尺寸和类型。
要想避免内存溢出,可以在解码图片之前检查位图的规模,除非你明确知道提供的图片资源在可获得的内存容量范围之内。
加载缩略版本到内存中
既然图片的规模是已知的,就可以决定是将图片完整地加载大内存中,还是加载一个缩小的样本。下面是一些需要考虑的因素:
1. 估算加载完整图片到内存中的内存使用量。
2. 在应用加载图片时,可以获得的内存容量。
3. 需要加载图片的目标ImageView或者UI组件的规模。
4. 当前设备的屏幕尺寸和像素。
例如,如果图片最终展示在一个128*96的指甲大小的ImageView中,那么加载一个1024*768像素的图片到内存是很不值的。
解码到一个图片样品中,加载一个缩小的版本到内存中,设置BitmapFactory.Option对象的inSampleSize值为true。比如,一张分辨率为2048*1536的图片可以被解码为四分之一大小的分辨率为512*384的样品图片。加载这张图片只需要0.75M内存而不是完整图片的12M内存(假设位图格式为ARGB_888),下面是一个计算样品尺寸值的方法,该方法基于二次幂相除得到一个width和height值。
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;
}
注意:要使用这个方法,首先通过设置isJustDecodeBounds值为true进行解码,将这个options传递进来,然后通过设置新的图片样品的isSampleSize的isJustDecodeBounds值为true再次进行解码。
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中变得更加容易,并且可以通过下列方法将图片展示到100*100像素的图片中。
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));