Android处理图片OOM的若干方法小结

前言

众所周知,每个Android应用程序在运行时都有一定的内存限制,限制大小一般为16MB或24MB(视平台而定)。因此在开发应用时需要特别关注自身的内存使用量,而一般最耗内存量的资源,一般是图片、音频文件、视频文件等多媒体资源;由于Android系统对音频、视频等资源做了边解析便播放的处理,使用时并不会把整个文件加载到内存中,一般不会出现内存溢出(以下简称OOM)的错误,因此它们的内存消耗问题暂不在本文的讨论范围。本文重点讨论的是图片的内存消耗问题,如果你要开发的是一款图片浏览器应用,例如像Android系统自带的Gallery那样的应用,这个问题将变得尤为突出;如果你开发的是目前的购物客户端,有时候处理不当也会碰到这种问题。

目前碰到的OOM场景,无外乎以下几种情形,不过无论是哪种情形,解决问题的思路都是一致的。

(1)显示单张图片,图片文件体积达到3000*4000级别的时候;

(2)在ListView或Gallery等控件中一次性加载大量图片时;

1.Java对象的引用类型

(1)强引用(StrongReference)如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

(2)软引用(SoftReference)如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

(3)弱引用(WeakReference)弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

(4)虚引用(PhantomReference)“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

解决OOM的常用方案

内存限制是Android对应用的一个系统级限制,作为应用层开发人员,没有办法彻底去消灭这个限制,但是可以通过一些手段去合理使用内存,从而规避这个问题。以下是个人总结的一些常用方法:

(1)缓存图像到内存,采用软引用缓存到内存,而不是在每次使用的时候都从新加载到内存;

(2)调整图像大小,手机屏幕尺寸有限,分配给图像的显示区域本身就更小,有时图像大小可以做适当调整;

(3)采用低内存占用量的编码方式,比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省内存;

(4)及时回收图像,如果引用了大量Bitmap对象,而应用又不需要同时显示所有图片,可以将暂时用不到的Bitmap对象及时回收掉;

(5)自定义堆内存分配大小,优化Dalvik虚拟机的堆内存分配;

本文主要将对前面4种方式做演示和分析。

演示试验说明

为了说明出现OOM的场景和解决OOM的方法,本人制作了一个Android应用——OomDemo来演示,此应用的基本情况说明如下:

(1)该应用展示一个gallery,该gallery只加载图片,gallery的adapter中传入图片的路径而不是图片对象本身,adapter动态加载图片;

(2)演示所用的图片预存储到sdcard的cache目录下,文件名分别为a.jpg,b.jpg…r.jpg,总共18张;

(3)图片为规格1920*1200的jpg图片,文件大小在423KB-1.48MB范围内;

(4)运行环境:模拟器——android2.2版本系统——480*320屏幕尺寸;Moto Defy——2.3.4版本CM7系统——854*480屏幕尺寸;

(5)程序基本结构图:

 \

演示结果与说明

1.演示一

首先采用最简单的图片加载方式,不带任何图片缓存、调整大小或者回收,SimpleImageLoader.class便是承担此职责。加载图片部分的代码如下:

@Override

public Bitmap loadBitmapImage(String path) {

       return BitmapFactory.decodeFile(path);

}

@Override

public Drawable loadDrawableImage(String path) {

       return new BitmapDrawable(path);

}

演示结果:在模拟器上图片只能加载1-3张,之后便会出现OOM错误;在Defy上不会出现错误;原因是两者内存限制不同,Defy上运行的是第三方ROM,内存分配有40MB。另外gallery每次显示一张图片时,都要重新解析获得一张图片,尽管在Defy上还未曾出错,但当图片量加大,GC回收不及时时,还是有可能出现OOM。

2.演示二

为图片加载的添加一个软引用缓存,每次图片从缓存中获取图片对象,若缓存中不存在,才会从Sdcard加载图片,并将该对象加入缓存。同时软引用的对象也有助于GC在内存不足的时候回收它们。ImageLoaderWithCache.class负责这个职责,关键代码如下:

private HashMap<String, SoftReference<Bitmap>> mImageCache;

       @Override

       public Bitmap loadBitmapImage(String path) {

              if(mImageCache.containsKey(path)) {

                     SoftReference<Bitmap> softReference = mImageCache.get(path);

                     Bitmap bitmap = softReference.get();

                     if(null != bitmap)

                            return bitmap;

              }

              Bitmap bitmap = BitmapFactory.decodeFile(path);

              mImageCache.put(path, new SoftReference<Bitmap>(bitmap));

              return bitmap;

       }

       @Override

       public Drawable loadDrawableImage(String path) {

              return new BitmapDrawable(loadBitmapImage(path));

       }

演示结果:在模拟器上,能不无缓存时多加载1-2张图片,但还是会出现OOM;在Defy上不曾出错。由于本次所用的图片都相对比较占内存,在GC还未来得及回收软引用对象时,就又要申请超出剩余量的内存空间,因此仍然没能完全避免OOM。如果换成加载大量的小图片,比如100*100规格的,缓存中软引用的作用可能就发挥出来了。(这一假设可以进一步试验证明一下)

3.演示三

为了进一步避免OOM,除了缓存,还可以对图片进行压缩,进一步节省内存,多数情况下调整图片大小并不会影响应用的表现力。ImageLoaderWithScale.class便是负责这个职责,调整大小的代码如下:

BitmapFactory.Options options = new BitmapFactory.Options();

       options.inJustDecodeBounds = true;

       BitmapFactory.decodeFile(path, options);

       if (options.mCancel || options.outWidth == -1 || options.outHeight == -1) {

              Log.d(“OomDemo”, “alert!!!” + String.valueOf(options.mCancel) + ” ” + options.outWidth + options.outHeight);

              return null;

       }

       options.inSampleSize = Util.computeSampleSize(options, 600, (int) (1 * 1024 * 1024));

       Log.d(“OomDemo”, “inSampleSize: ” + options.inSampleSize);

       options.inJustDecodeBounds = false;

       options.inDither = false;

       options.inPreferredConfig = Bitmap.Config.ARGB_8888;

       Bitmap bitmap = BitmapFactory.decodeFile(path, options);

演示结果:在上述代码中,首先解码图片的边界,在不需要得到Bitmap对象的前提下就能获得图像宽高(宽高值分别被设置到options.outWidth和options.outHeight两个属性中)。computeSampleSize这个方法的参数分别为“解析图片所需的BitmapFactory.Options”、“调整后图片最小的宽或高值”、“调整后图片的内存占用量上限”。结合原始图片的宽高,此方法可以计算得到一个调整比例,再用此比例调整原始图片并加载到内存中,此时图片所消耗的内存不会超出事先指定的大小。在模拟器中,限制图片所占内存大小为1*1024*1024时,比未压缩过时能加载更多图片,但仍然会出现OOM;若限制图片所占内存大小为0.5*1024*1024,则能完整的载入所有图片。所以调整图片大小还是能够有效节省内存的。在Defy中不会出错,原因同上。

4.演示四

在有些情况下,严重缩小图片还是会影响应用的显示效果的,所以有必要在尽可能少地缩小图片的前提下展示图片,此时手动去回收图片就变得尤为重要。在类ImageLoaderWithRecyle.class中,便增加了回收图片资源的方法:

@Override

       public void releaseImage(String path) {

              if(mImageCache.containsKey(path)) {

                     SoftReference<Bitmap> reference = mImageCache.get(path);

                     Bitmap bitmap = reference.get();

                     if(null != bitmap) {

                            Log.d(“OomDemo”, “recyling ” + path);

                            bitmap.recycle();

                     }

                     mImageCache.remove(path);

              }

       }

演示结果:图片压缩限制仍然维持在1*1024*1024,在adapter中,及时调用releaseImage方法,回收暂时不需要的图片。此时模拟器中也从未出现过OOM,所以总的来讲,综合缓存、调整大小、回收等各种手段,还是能够有效避免OOM的。

小结

本文介绍了软引用缓存、调整大小、回收等手段来避免OOM,总体来说效果还是明显的。但实际应用场景中,图片的应用不想本文所演示的那样简单,有时候图片资源可能来自与网络,这时需要配合异步加载的方式先下载图片并通过回调的方法来显示;有时候图片资源还需要加边框、加文字等额外修饰,所以在图片加载之后还要另做处理。

另外由于本人能力所限以及时间关系,本文还有诸多不完善之处。比如对Android内存分配的理解不深,没能透彻地解释Bitmap的内存占用情况;通过自定义堆内存分配大小,优化Dalvik虚拟机的堆内存分配的方法来解决OOM,本文也没有给予演示;再比如在上文的演示试验里,没有把内存占用情况的详细信息用图像形式直观地展示出来;还有演示所用的图片数量过少、规格单一、测试环境偏少,所有没能进行更加严谨科学的对比试验,遗漏了某些意外情况。最后欢迎大家来共同探索、交流并提出建议。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值