很多时候在使用BitmapFactory.decode*解码图片的时候会出现内存不足。主要有以下几个原因导致这个问题:
- 移动设备通常都限制了系统资源,一般每一个应用最小可以限制到16M。应用程序应该优化到应用能在这个最小内存正常执行,但是,很多设备都配置更高的限制。
- Bitmap占用了大量的内存,尤其是内容丰富的图像照片。例如,在Galaxy Nexus One的相机拍摄的照片达2592x1936像素(5百万像素)。如果使用Bitmap的配置是ARGB_8888(从Android2.3以后的默认设置),然后加载这个图像到内存大约需要的内存(2592*1936*4字节)19MB,应用程序在某些设备上使用马上就有了限制。
- Android应用程序用户界面的经常需要几个Bitmap一次载入。如ListView的组件,GridView和ViewPager通常包括多个Bitmap在屏幕上。
知道了原因我们可以针对这些原因来优化,比如现在网络上的图片是1280×960,那么使用ARG_8888的方式加载到内存就会占用1280×960×4 =约4.9M,但是我们在设备上显示的时候不可能使用这么大的分辨率,因为比如一个头像可能的大小是128×96或者其他分辨率,加载一个完整的大图就是浪费内存。所以我们可以通过
BitmapFactory.Options
类指定解码选项来解码缩小后的图片到内存。
BitmapFactory.Options
类有很多属性,这里会用到的就是outWidth,outHeight分别对应的是图片的宽和高,inJustDecodeBounds这个属性非常重要,因为它可以让我们不加载图片到内存就能知道图片的大小和类型。设置inJustDecodeBounds
是true,可以避免在解码的时候分配内存,会返回Null的Bitmap对象,但是会设置 outWidth
, outHeight
和outMimeType
.可以像下面这样获取到图片的大小:
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;
到此为止我们就获取到了即将要加载到内存的图片的尺寸,接下来我们会用到BitmapFactory.Options中一个非常有用的属性:inSampleSize。它的作用是告诉解码器解码图片的时候的模板量,什么意思呢?简单的理解它是一个量词,默认它是1,如果你设置成2,那么解码出来的图片的大小会减小一半,也就是说100×100的图片如果使用inSampleSize=2的选项去加载,那么解码出来的图片就是50×50.这个属性有一个非常重要的特点就是解码器在解码的时候会四舍五入这个属性最接近2的幂(意思就是我们使用的时候这个属性最好是2的幂)。google官方推荐了一种计算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;
}
好了,我们已经获取到了即将加载图片的尺寸和需要缩放的模板量,接下来我们把inJustDecodeBounds设置成false,把inSampleSize设置成我们计算出来的大小,然后再一次通过BitmapFactory.decode*解码图片就完成了。
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);
}
当然解码的这个方法不要放到UI线程去操作,因为我们不能确定会加载多长时间。
如果有什么问题,或者需要指教的地方请一定告诉我。