转载请注明 http://blog.csdn.net/sinat_30276961/article/details/47303539
几乎所有Android应用都会涉及到图片,而Android图片的展示是通过Bitmap的。我们开发过应用的都或多或少了解到Bitmap的使用有诸多麻烦,使用不当就是OOM,即OutofMemoryError。那么怎么确保既能开心的使用大量的Bitmap,又不会出现OOM呢?
Google官方的doc给了我们很好的方案,我计划会把bitmap优化分为几篇进行讲解。
我们先来看看为啥要关注bitmap使用优化:
1.移动设备通常有限制的系统资源。比方说,Android设备可以限定每个应用只能最多分配16M。也就是说你的应用必须优化到占用内存少于16M。
2.Bitmap恰好是占用内存的大头。比方说,Galaxy Nexus拍张照像素最高可到 2592x1936。然后bitmap默认使用ARGB_8888去解析颜色。那么这张图就占用大概19M(2592*1936*4 bytes)。这样,立马就超了刚才举例的内存限制。
3.Android ui经常大量的使用bitmap。比方说,listview,gridview,viewpager。一般在页面上要展现很多bitmap。还包括未展现,但即将被滑到的那部分。
在很多种情况下,我们界面所要展现的图片,往往比实际的图片像素要求低。而且用这些高像素的图片放到低像素要求的界面上,也不会有显示效果上的优势。反倒会消耗大量内存,影响效率,因为,这个过程需要缩放。
读取bitmap的规模和类型
BitmapFactory提供一系列针对不同数据源的解析方法(decodeByteArray(), decodeFile(), decodeResource()等等)。这些方法在构造bitmap的时候会分配内存,所以,很容易造成OOM。每个解析方法都提供了一个可选的参数BitmapFactory.Options用来定义解析的属性。可以通过设置inJustDecodeBounds这个属性为true,来阻止解析时分配内存,此时解析返回null,但是赋值了options内部outWidth,outHeigth和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;
除非你十分肯定你解析的图片很合适,不会超出内存使用范围,否则你还是得按照上面的方式来预解析下。
内存加载缩小版
根据上面的技术,已经得到图片规格。然后你可以通过这些尺寸,决定是否直接加载,还是加载缩小版。这里罗列一些考虑因素:
1.评估加载完整图片所需内存
2.为了加载这张图片你愿意提供多少内存
3.展示这张图的控件(Imageview或其他)的尺寸大小
4.当前屏幕的尺寸和屏幕像素密度
比方说实际展示的控件尺寸只有128x96px,你却去解析一个1024x768 px的图片给它,这样是不划算的。
那么怎么解析成缩小版的图片,只要通过设置BitmapFactory.Options的inSampleSize的值就行。比方说,原先是2048x1536的,通过设置inSampleSize值为4,就会解析成512x384。这在ARGB_8888情况下,相当于从原先要加载12MB,变为加载0.75MB。下面给出通过目标要求的宽和高,来计算出inSampleSize的代码:
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;
}
使用2的倍数增加是因为解析时使用的最终值大概是2的倍数,这样增加解析效率。
在使用这个方法前,先要通过设置inJustDecodeBounds为true,解析一次,获取到options的图片原始宽高。然后计算出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);
}
使用上面的方法,可以很轻松的把大分辨率图片以100x100的分辩率加载到ImageView,如下代码所示:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
上述图片源是资源文件形式,其他图片源形式的解析过程和上述类似,只要使用正确的解析方法就行BitmapFactory.decode*
下一篇Android Bitmap大量使用不产生OOM之多线程并发加载Bitmap的处理方式,如果有兴趣,可以进去看看。