Android中通过Bitmap对象来使用图片,在加载Bitmap对象的时候,可能会导致UI线程被阻塞,用户体验差或者ANR问题;Bitmap对象迅速的消耗掉大量的内存,出现OutOfMemory异常等问题。在Android应用在加载Bitmap的时候,由于以下原因,我们需要特别的小心处理:
1.移动设备的内存资源有限,我们应该尽量的提高内存的使用效率,避免耗尽内存导致程序崩溃。
2.Bitmap对象会消耗大量内存。图片在以文件的形式存储时,可能会很小,但是当其以Bitmap的形式加载进内存的时候,将会占用大量内存,其计算公式为
图片长度像素数*图片宽度像素数*每像素需大小,即与图片的分辨率和Bitmap配置有关。
从Android 2.3开始,使用ARGB_8888作为Bitmap的默认配置,该配置下,每个像素需要使用4Byte来表示。以我手机拍摄的一张照片为例,其分辨率为 4160*2340,大小为5.89M,当其加载进内存的时候,需要4160*2340*4/1024/1024 约为37.13M(Bitmap对象还需要保存一些关于图片的数据信息),由此可见,Bitmap对象对内存的消耗是非常巨大的。
3.在Android应用中,我们可能会在UI中同时加载多张图片,如ListView,GridView,ViewPager等
因此,我们需要对Bitmap的使用保持谨慎
在Android加载图片的过程中,我们通常使用多种优化手段来提高内存的使用效率,改善用户体验,一般来说有以下方法:
1.高效加载大图
2.在非UI线程处理Bitmap对象
3.缓存Bitmap
4.管理Bitmap的内存使用
1.高效加载大图
一般情况下,我们需要加载的图片的分辨率都会超过我们需要呈现的大小很多,例如,现在手机的摄像头1300万像素已经是标配了,其拍摄的照片的分辨率远远超过了手机屏幕的分辨率。加载一个超过屏幕分辨率的照片没有什么显而易见的好处,除非我们有特别的需求,而且这将会浪费大量的内存资源,甚至导致耗尽内存。通常我们通过对图片的缩放来获取一个与UI控件大小匹配的低分辨率的照片来进行显示。这将会大大的降低对内存的消耗,而且也不会降低用户的体验。
BitmapFactory提供了一些decode方法(decodeFile(),decodeStream(),decodeResource(),decodeByteArray())来从不同的资源中创建Bitmap,这些方法在创建Bitmap的时候会尝试分配内存,因此容易导致OOM异常的发生。因此,在通过这些方法进行解码的时候,我们可以传递一个BitmapFactory.Options对象,来指定一些设置,从而避免直接进行内存分配,同时获取图片的分辨率以及类型信息。
BitmapFactory.Options options = new BitmapFactory.Options();
//inJustDecodeBounds属性被设置为true时,解码将不会进行内存分配,返回的Bitmap为null
//但是解码后Options的out*属性将会得到相应的数据,调用者可以使用这些数据。
options.inJustDecodeBounds = true;
//在5.0后这些设置被废弃了,具体作用请查看文档
//options.inPurgeable = true;
//options.inInputShareable = true;
BitmapFactory.decodeFile("/storage/sdcard0/two.jpg",options);
//解码后,可以查看Options中的out*属性的信息了
int height = options.outHeight;
int width = options.outWidth;
String mineType = options.outMimeType;
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
//默认缩放值为1,不缩放
int inSampleSize = 1;
//获取完整图片的分辨率大小
int rawWidth = options.outWidth;
int rawHeight = options.outHeight;
//如果完整图片的宽高至少有一项大于目标宽高值,那么将计算缩放
//否则将不进行缩放
if(rawWidth > reqWidth || rawHeight > reqHeight){
int halfwidth = rawWidth / 2;
int halfHeight = rawHeight / 2;
//循环计算inSampleSize值,直到完整图片宽高与该值相除后至少有一项不大于目标宽高
while((halfHeight / inSampleSize) > reqHeight
&& (halfwidth / inSampleSize) > reqWidth){
inSampleSize *= 2;
}
}
return inSampleSize;
}
注意:缩放的值一般设置为2的幂数,因为对于非 2的幂数的值,解码器还会进行进一步处理,以获取一个最接近该值的2的幂数。
inSampleSize的值最小设置为1,此时不缩放,小于1的情况下将视为1处理。
在获取到恰当的缩放比例后,我们就可以得到一个缩放后的低分辨率版本的Bitmap了
public static Bitmap loadSampledBitmapFromFile() {
BitmapFactory.Options options = new BitmapFactory.Options();
// inJustDecodeBounds属性被设置为true时,解码将不会进行内存分配,返回的Bitmap为null
// 但是解码后Options的out*属性将会得到相应的数据,调用者可以使用这些数据。
options.inJustDecodeBounds = true;
// 在5.0后这些设置被废弃了,具体作用请查看文档
// options.inPurgeable = true;
// options.inInputShareable = true;
BitmapFactory.decodeFile("/storage/sdcard0/two.jpg", options);
// 解码后,可以查看Options中的out*属性的信息了
int height = options.outHeight;
int width = options.outWidth;
String mineType = options.outMimeType;
// 计算恰当的缩放比例
options.inSampleSize = calculateInSampleSize(options, 200, 200);
// 解码时分配内存,创建Bitmap对象
options.inJustDecodeBounds = false;
// 获取低分辨率版本的Bitmap对象
return BitmapFactory.decodeFile("/storage/sdcard0/two.jpg", options);
}