有效解决Android加载大图片时内存溢出的问题

首先解析一下基本的知识:

位图模式,bitmap颜色位数是1位

灰度模式,bitmap颜色位数是8位,和256色一样

 

RGB模式,bitmap颜色位数是24位 在RGB模式下,一个像素对应的是红、绿、蓝三个字节

CMYK模式,bitmap颜色位数是32位  在CMYK模式下,一个像素对应的是青、品、黄、黑四个字节

图像文件的字节数(Byte) = 图像分辨率*颜色深度/8(bit/8)

例如:一幅640*480图像分辨率、RGB色一般为24位真彩色,图像未经压缩的数据容量为:640X480X24/8=921600字节=900KB(1KB=l千字节=1024字节)。
注:一个图像文件占的磁盘空间大小还和磁盘的文件格式有关。如:NTFS最小单位为4KB 所以图像文件大小肯定是4KB的倍数。但是有图图片压缩算法的存在,图片文件在保存时,体积要比在内存的大小小得多,如640x480的图片文件大小一般只在200K~300K。这也是为什么,加载几MB的图片文件,会导致JVM内存溢出,导致OutofMemoryException的原因。

 

由上面的公式,我们可以得出,加载的图片所占的内存大小,取决于其分辨率颜色数

 

我们再来看看Google官方的介绍:

 

这个已经非常的明白了,我们VM的app进程所获得的内存只有区区的16MB,普普通通的5MP摄像头拍出来的图片,直接加载,将占用接近19MB的内存。可见,不进行压缩,内存将会直接溢出。

 

再了解一下,android读取解析图片的方式,基本与Java的方式类型,通过文件输入流,然后进行解码,再转成图片格式;

 

当然google的android也为我们封装好了若干方法,来方便快捷地完成这项工作,如ImageView的setImageBitmap,setImageResource,BitmapFactory的decodeResource等,但是尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存;

 

因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的source,加载显示。decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。

 

在使用 decodeStream 读取图片时,再加上Config参数,就可以更有效地控制加载目标的内存大小,从而更有效阻止抛 OutofMemoryException异常,下面用一段代码说明:
public static Bitmap readBitmapAutoSize(String filePath, int outWidth, int outHeight) {  
                //outWidth和outHeight是目标图片的最大宽度和高度,用作限制
		FileInputStream fs = null;
		BufferedInputStream bs = null;
		try {
			fs = new FileInputStream(filePath);
			bs = new BufferedInputStream(fs);
			BitmapFactory.Options options = setBitmapOption(filePath, outWidth, outHeight);
			return BitmapFactory.decodeStream(bs, null, options);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				bs.close();
				fs.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}

private static BitmapFactory.Options setBitmapOption(String file, int width, int height) {
		BitmapFactory.Options opt = new BitmapFactory.Options();
		opt.inJustDecodeBounds = true;          
                //设置只是解码图片的边距,此操作目的是度量图片的实际宽度和高度
		BitmapFactory.decodeFile(file, opt);

		int outWidth = opt.outWidth; //获得图片的实际高和宽
		int outHeight = opt.outHeight;
		opt.inDither = false;
		opt.inPreferredConfig = Bitmap.Config.RGB_565;    
                //设置加载图片的颜色数为16bit,默认是RGB_8888,表示24bit颜色和透明通道,但一般用不上
		opt.inSampleSize = 1;                          
                //设置缩放比,1表示原比例,2表示原来的四分之一....
                //计算缩放比
		if (outWidth != 0 && outHeight != 0 && width != 0 && height != 0) {
			int sampleSize = (outWidth / width + outHeight / height) / 2;
			opt.inSampleSize = sampleSize;
		}

		opt.inJustDecodeBounds = false;//最后把标志复原
		return opt;
	}

另外,decodeStream直接拿的图片来读取字节码了, 不会根据机器的各种分辨率来自动适应, 使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源, 否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。 可参考下面的代码:
BitmapFactory.Options opts = new BitmapFactory.Options();
//设置图片的DPI为当前手机的屏幕dpi
opts.inTargetDensity = ctx.getResources().getDisplayMetrics().densityDpi;  
opts.inScaled = true;
另外,图片的bitmap对象为大对象,不用了要注意主动回收,
 if(!bmp.isRecycle() ){
         bmp.recycle()   //回收图片所占的内存
         system.gc()  //提醒系统及时回收 
}
占用内存对比测试:
BitmapFactory.decodeResource后的内存占用情况:
//加载图片前的空余内存空间
    long freeStart = Runtime.getRuntime().freeMemory();
    bubble2 = BitmapFactory.decodeResource(resources, R.drawable.bubble2);
    bubble5 = BitmapFactory.decodeResource(resources, R.drawable.bubble5);
    bubble_2 = BitmapFactory.decodeResource(resources, R.drawable.bubble_2);
    speeding = BitmapFactory.decodeResource(resources, R.drawable.speeding);
    slowing = BitmapFactory.decodeResource(resources, R.drawable.slowing);
    resee = BitmapFactory.decodeResource(resources, R.drawable.resee);
    network = BitmapFactory.decodeResource(resources, R.drawable.network5);
    audio = BitmapFactory.decodeResource(resources, R.drawable.audio);
    eye_back = BitmapFactory.decodeResource(resources, R.drawable.eye_back);
    eye = BitmapFactory.decodeResource(resources, R.drawable.eye);
    //加载图片后的空余内存空间
    long freeEnd = Runtime.getRuntime().freeMemory();
  System.out.println("freeStart:"+freeStart+"\nfreeEnd:"+freeEnd+"\n 相差:"+(freeStart-freeEnd));
   运行结果是:
   Android加载大图片内存溢出的问题总结
再来看看使用BitmapFactory.decodeStream的情况:
    再来看看使用BitmapFactory.decodeStream的情况:
public Bitmap readBitmap(Context context, int id){
     BitmapFactory.Options opt = new BitmapFactory.Options();
     opt.inPreferredConfig=Bitmap.Config.RGB_565;//表示16位位图 565代表对应三原色占的位数
     opt.inInputShareable=true;
     opt.inPurgeable=true;//设置图片可以被回收
     InputStream is = context.getResources().openRawResource(id);
     return BitmapFactory.decodeStream(is, null, opt);
}    


//加载图片前的空余内存空间
long freeStart = Runtime.getRuntime().freeMemory();
bubble2 = utils.readBitmap(context, R.drawable.bubble2);
bubble5 = utils.readBitmap(context, R.drawable.bubble5);
bubble_2 = utils.readBitmap(context, R.drawable.bubble_2);
speeding = utils.readBitmap(context, R.drawable.speeding);
slowing = utils.readBitmap(context, R.drawable.slowing);
resee = utils.readBitmap(context, R.drawable.resee);
network = utils.readBitmap(context, R.drawable.network5);
audio = utils.readBitmap(context, R.drawable.audio);
eye_back = utils.readBitmap(context, R.drawable.eye_back);
eye = utils.readBitmap(context, R.drawable.eye);
//加载图片后的空余内存空间
long freeEnd = Runtime.getRuntime().freeMemory();
System.out.println("freeStart:"+freeStart+"\nfreeEnd:"+freeEnd+"\n 相差:"+(freeStart-freeEnd));
 运行结果是:
        Android加载大图片内存溢出的问题总结

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值