图片有不同的形状和像素。在很多情况下,都远远大于程序要求的标准大小。比方说,图库应用展示的相机拍照的图片,相机的解析率一般都比你的屏幕密度要高。
假定你的APP工作在有限内存的环境下,理想状态中你只是想载入一张低分辨率的图片,这张低分辨率的图片的尺寸要和将要显示它的控件相一致。一个高分辨率的图片,除了提供了高质量的可见度,而且还占用了珍贵的内存,而且额外的动态宽展也导致了高的性能消耗。
这一节里面就讲述一下如何在不超过每个APP内存限制的情况下,通过解析大的位图,得到一个小的版本,载入到内存中。
读取位图的大小和类型
BitmapFactory
类提供了一些解析bitmap的方法(decodeByteArray()
, decodeFile()
,decodeResource()
等等)利用各种资源来创建一个Bitmap.选择哪种最合适的方法要根据的bitmap类型来决定。这些方法会分配一块内存来创建的bitmap,所以很容易导致内存溢出的问题。每一个方法都有一个额外的签名,这个可以让你通过BitmapFactory.Options来指定解析操作。设置
inJustDecodeBounds属性为true,来避免内存的分配,会返回一个null,但是设置了
outWidth
, outHeight
and outMimeType,这个方法允许你在创建bitmap之前得到这个位图的分辨率和类型。
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这个内存溢出,最好在解码bitmap之前先判断它的分辨率,除非你很确定在有限的内存下,你知道这个图像的资源可以提供合适的大小的图片。
载入缩放的图片到内存
现在知道图像的分辨率了,你就可以决定完全载入这个完成的图像还是说载入它的缩放的版本。下面是一些考虑的因素:
估算要载入整个图像需要的内存
考虑你的APP里面其他地方的需要的内存之外,你可以用来解码图像的内存数量
- ImageView的分辨率或者说图像要载入的部件的分辨率
- 当前设备的屏幕尺寸和密度
比方说,如果我们只想在128*96大小的ImageView里面显示一个图像,那就没有必要载入一个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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
注意:让2为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);
}
这个方法可以让你的程序载入任意大小的图像,可以显示到拇指大小的100*100的ImageView里面,如下面代码所示:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
可以按照类似的方法,从其他的资源文件里面解码位图,使用不同的BitmapFactory.decode* 方法。