完美的去加载bitamap,不但可以增加用户体验度,还能避免内存溢出。加载bitmap,一部小心就会导致应用crash.报出下面的异常。
java.lang.OutofMemoryError: bitmap size exceeds VM budget.
如果需要加载图片比较大。很容易造成内存溢出,如何去防止内存溢出呢。
读取bitmap的尺寸和类型
BitmapFactory类提供了很多decoding方法(decodeByteArray(), decodeFile(), decodeResource()等)去加载bitmap从不同的数据源,根据图片的来源去选择合适的方法。这些方法在会占用很多内存去构造bitmap,因此,容易导致oom异常。我们如何去避免OOM异常呢。
每个方法有BitmapFactory.Options类,这个类中有个成员变量为inJustDecodeBounds,当我们设置它为true时,再去加载图片,则会避免OOM。这样做后,返回的bitmap为null。但是会返回图片的outWidth, outHeight 和outMimeType。那大家就疑惑了,我们就是为了创建bitmap,而现在返回的bitmap为null,有何用。其实这个就是为了在创建bitmap前获取图片的尺寸和类型。
为了避免OOM,在创建bitmap前,我们先去检查它的尺寸,除非,你绝对的有把握,现在app的内存完全足够去加载你想要decode的图片。
下面是获取图片尺寸和类型的代码:
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;
加载缩放后图片到内存
现在,我们获取到了图片的尺寸,我们就可以知道我们是直接加载原图到内存,还是缩放后再去加载。
距离来说:现在有张1024x768像素的图片,但是我们最终只需要展示一张128x96是像素缩略图即可。
现在有张图片,原图大小为2048x1536,通过inSampleSize设置缩放4倍后,图片大小为512x384,加载原图需要12MB(假设图片的配置为ARGB_8888),而加载缩放后的图片需要0.75MB。下面这个方法就是的来计算缩放比例:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 原图的宽高
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;
}
我们发现上面的代码中计算缩放比取值用2的倍数。因为最终构建bitmap时,取值为2的倍数,舍尾。
利用上面这个方法,首先设置inJustDecodeBounds为true,获取到缩放比inSampleSize 的值后,再设置inJustDecodeBounds为false,最终利用bitmapFactory类中的方法去获取bitmap.
下面为获取bitmap的完整过程。
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));
可以看到,经过以上的封装,我们可以非常容易的去加载任意尺寸的图片。