当在android应用中加载一张高分辨率的图片时,十分容易出现Out of memory(OOM),这是由于内存溢出造成的,每个应用所使用的堆内存大小一般是固定的,有的是16M,有的可能会大些。那为什么这么大内存加载一张图片会溢出呢?原因就是android在加载图片的时候是使用位图来放到内存中的,那位图在内存中的占用空间计算就是 分辨率*每个像素占用的内存(
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存
RGB_565:每个像素占用2byte内存),
举个例子,如果一个图片的分辨率是1024*768,采用ARGB_8888,那么占用的空间就是1024*768*4=3MB,这张图片需要占用3M的内存空间,对于这样的图片,如果只加载一样的话,内存还能应付的过来,如果分辨率更高呢,特别是相机的照片,例如:3648*2736的一样照片,内存占用为3648*2736*4=33MB,这一张图片就是占用33MB的空间,肯定会导致内存溢出。那应该如何处理呢?
第一,降低图片加载到内存时的图片大小(分辨率)。
第二,采用更节省内存的编码,例如ARGB_4444。
第三,如果是加载大量图片的话,还可以采用缓存(这里不讨论)。
我们主要说第一和第二如何实现。
- 降低图片大小,由于移动设备的屏幕尺寸有限,即使将高分辨率图片的分辨率降低,也不会影响显示效果。那如何降低呢?就需要用到一个类,那就是BitmapFactory.options类,主要会用到这个类的inSampleSize、inJustDecodeBounds、outHeight、outWidth参数。
inSampleSize:缩放比例,这个参数需要是2的幂函数。
inJustDecodeBounds:如果设置这个参数为ture,就不会给图片分配内存空间,但是可以获取到图片的大小等属性。
outHeight:图片高,单位像素.
outWidth:图片宽,单位像素.
说完这个类后,我们说说步骤,首先我们可以通过设置一个Options的属性inJustDecodeBounds=true,然后使用BitmapFactory.decodeXXX方法,让options作为参数,这样,我们在不分配内存的情况下,可以通过options读取图片的大小,outWidth和outHeight。其次通过原始图片的大小和你需要图片的大小来计算出需要缩放的比例。最后通过缩放比例值作为options.inSampleSize的值,再次调用BitmapFactory.decodeXXX,在调用方法前一定要设置inJustDecodeBounds=faluse。
- 采用节省内存的编码方式。在Options中,有一个inPreferredConfig属性,这个属性的值是一个Bitmap.Config,我们只需要给inPreferredConfig设置一个节省内存的编码就可以了,例如:options.inPreferredConfig=Bitmap.Config.ARGB_4444.这样就ok了。
举例如下:
计算缩放比例方法(android文档上提供的)
- public int calculateInSampleSize(BitmapFactory.Options op, int reqWidth,
- int reqheight) {
- int originalWidth = op.outWidth;
- int originalHeight = op.outHeight;
- int inSampleSize = 1;
- if (originalWidth > reqWidth || originalHeight > reqheight) {
- int halfWidth = originalWidth / 2;
- int halfHeight = originalHeight / 2;
- while ((halfWidth / inSampleSize > reqWidth)
- &&(halfHeight / inSampleSize > reqheight)) {
- inSampleSize *= 2;
- }
- }
- return inSampleSize;
- }
从资源图片加载一个大图片,并设置为一个ImageView(iv)的图片。
- BitmapFactory.Options options = new Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeResource(rs, R.drawable.a2,options);
- options.inPreferredConfig = Bitmap.Config.ARGB_4444;
- options.inSampleSize = calculateInSampleSize(options, 200, 200);
- options.inJustDecodeBounds = false;
- Bitmap bitmap = BitmapFactory.decodeResource(rs, R.drawable.a2,options);
- iv.setImageBitmap(bitmap);
你可以做一个对比测试,在decodeResource方法中不用options参数和使用options,通过DDMS看看堆内存的使用情况。然后在对比一下使用ARGB_4444和ARGB_8888,堆内存的使用情况。
我在测试的时候,有一个特殊情况,我在drawable-hdpi目录中和drawable-mdpi目录中,存放两张一样的图片(分辨率为3648*2736),采用同样的代码,读取drawable-mdpi中图片,缩放正常,例如inSampleSize=4,屏幕输出的图片分辨率为912*684,但是读取drawable-hdpi目录中的同样一张图片,inSampleSize=4,输出图片的分辨率为608*456,这个十分不解。