先看看这个异常:
java.lang.OutofMemoryErroe:bitmap size exceeds VM budget
在开发时常遇到,android对单个应用的内存限制在16MB,但是现在手机种类繁多,各个厂商不同的手机版本和型号有所不同分配的内存也不同。在加载Bitmap的时候很容易造成内存溢出。因此高效地加载Bitmap是一个很重要也很容易被我们忽视的问题。
在Android中Bitmap指的是一张图片,可以是png、jpg格式等常见的图片格式,如何加载一个Bitmap呢?
BitmapFactory类提供了四类方法:decodeFile、decodeStream、decodeResource、decodeByteArray,分别从文件系统,输入流,资源,字节数组中加载一个Bitmap对象,其中decodeFile和decodeResource又间接调用了decodeStream方法,这四类方法最终在android底层实现的。对应着BitmapFactory类的几个native方法。
高效的加载一个Bitmap需要采用BitmapFactory.options来加载所需尺寸的图片,开发中我们使用的控件往往没有(例如:ImageView)图片的原始尺寸那么大,这时把这个图片加载进来在设置给控件显然没必要,因为控件并没有办法显示原始图片。通过BitmapFactory.options就可以按一定的采样率加载缩小图片,将缩小后的图片在控件上显示,这样会降低内存占用在一定程度上避免OOM.
通过BitmapFactory.options来缩放图片,主要是用到它的inSampleSize参数,即采样率。inSampleSize的取值应该总是2的指数,例如 1、2、4、6、8、16、32……等。比如为2时,采样后的图片大小为原图大小1/2,而像素为原图的1/4,占有内存大小也为原图的1/4.拿一张1024*1024像素的图片来说,假定采用ARGB8888格式存储,那么它占用内存为1024*1024*4,即 4 MB,如果inSampleSize为2,那么采样后的图片内存占用只有512*512*4,即 1 MB,如果外界传递给系统的inSampleSize不为 2 的指数,那么系统会向下取整应选择一个最接近的 2 的指数代替,比如3,系统会选择2 来代替。但是这个结论并非在所有的android版本上都成立。因此只是一个开发建议。
通过采样率即可有效地加载图片,开发中我们如何获取采样率呢?
主要分四步:
1.将BitmapFactory.options的inJustDecodeBounds参数设为true,并加载图片。
2.从BitmapFactory.options中取出图片的原始宽高信息,这个操作是轻量级的,并不会真正去加载图片。它们对应于outWidth 和 outHeight参数。
3.根据采样率的规则并结合目标View的所需大小计算inSampleSize.
4.将BitmapFactory.options 的inJustDecodeBounds参数设为false,重新加载图片。
将上面的4个流程用程序实现,代码:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth ,int reqHeight){
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resId, options);
//Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId,options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight){
final int height = options.outHeigth;
final int width = optiosn.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth ){
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >=reqWidth.){
inSampleSize *=2;
}
}
return inSampleSize;
}
我们在实际使用时就更简单了,比附ImageView所期望的图片大小为100*100像素,这个时候就可以通过如下方式高效加载并显示图片:
mImageView.stImageBitmap(decodeSampleBitmapFromResource(getResource(), R.id.icon,100,100));
BitmapFactory的四个方法都支持采样加载,并且处理方式类似。但是decodeStream方法稍微特殊。