载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-)

http://my.oschina.net/ryanhoo/blog/93285

摘要在我翻译的Google官方系列教程中,Bitmap系列由浅入深地介绍了如何正确的解码Bitmap,异步线程操作以及使用Fragments重用等技术,并且在最后给出了非常强大的独家秘笈:BitmapFun,让猿媛们得以一窥究竟Google的攻城师们是如何高屋建瓴地秒杀OOM的。

前言

在下载到BitmapFun.rar这个神圣的压缩包以后,我是双手颤抖,似乎是打开上古秘藏一般,心情激动导致久久不能自已。我还记得那天上海下着小雨,我当时霍然起身,伫立在23楼的窗台,仰着头向江水对岸的东方明珠望去,似乎这样我郁积已久的眼泪就不能掉下来。说到这里,Ryan又暗自抹了一把眼泪。短暂地忘记了过去的黑暗时光,那一个漫长的被OOM的淫威所折磨的盛夏。。。

后在Boss诧异的目光中,我回到办公桌,按捺着内心汹涌的情绪波动,然后小心翼翼的打开BitmapFun.rar。当那些在洪荒时代就活跃在Android平台的大师们书写的篇章呈现在我眼前时,我的表情与阿宝从师父手里得到Dragon Scroll时一般,永久的定格在了极度天真的期待与眼角一抽一抽的状态。

那些泛黄的代码在我看去,通篇只有一句话:老子看不懂!

自力更生,构建自己的缓存模块

Google的这个demo堪称详尽,考虑极其周详,自然是极好的。但是当原理被层层的“特殊情况”包装起来,原本简单的例子便得异常复杂,几个类之间的关系错综复杂,堪比吸血鬼日记几个帅哥美女之间的关系。要理解清楚每一句代码的含义,你一定要有理解Matt那人老珠黄的老娘和他和失落的好朋友Taylor搞在一起的觉悟。

好了,吐槽一下就收,千万不要怀疑Google,人家已经仁至义尽了。BitmapFun中在下载后将Bitmap缓存起来,缓存做了两份:LruCacheDiskLruCache,分别是内存缓存硬盘缓存。此外两个至关重要的类是:

1 BitmapWorkerTask(ImageView imageView)
2
3 AsyncDrawableextendsBitmapDrawable
4 AsyncDrawable(Resources res, Bitmap bitmap,BitmapWorkerTask bitmapWorkerTask)

BitmapWorkerTask持有一个WeakReference<ImageView> imageViewReference,弱引用ImageView,用作异步处理加载图片的任务。

AsyncDrawable巧妙的引用持有弱引用WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference,是BitmapDrawable的子类,这样就可以setImageBitmap(AsyncDrawable)

关系:AsyncDrawable中弱引用BitmapWorkerTask。其实是图片引用ImageView的关系,而ImageView.getDrawable又可以获得图片。这种高妙的思想不是正值得我们学习么?

当然,这节课并不是讲解官方Demo的,在讲解它之前,我们先来学习一个更加简单的缓存实现方案,使用最简单的方式快速构建自己应用的缓存模块,有效避免OOM异常。它的难度非常小也很方便理解,可以在这个缓存实现的基础上,我们再去理解更加高妙的BitmapFun的缓存实现方案。

后面将要介绍的缓存方案已经应用在一个的项目中(该项目将于13年1月20开源,使用Github托管,纪念我22岁的生日),效果相当不错,下载并显示上百张Bitmap也异常流畅,甚至没有半点的停顿,全程使用Emulator测试也没有出现过OOM异常,内存处于可控状态。

如何解决OOM

Bitmap之所以容易引起OOM异常,原因已经在Bitmap系列教程中说的明明白白。但是我们至少清楚一点:一个手机屏幕再大,合理尺寸的Bitmap也不至于耗空所有内存,那要怎么做才能避免OOM呢?

  • 加载合理尺寸的Bitmap
  • 避免反复解码、重复加载Bitmap
  • 控制Bitmap的生命周期,合理回收

此外网上也有不少歪门邪道,我个人认为是不可取的,使用这些简单粗暴的方法,后期会为你带来更大的麻烦

  • 减损图片质量(使用过高的inSampleSize值)
  • 使用decodeStream(绕过Java层,直接调用JNI)
  • 强制增加heap size
  • 其他

控制Bitmap的生命周期才是正解,BitmapFun使用的LruCache是它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉

Memory Cache的Size是受限的,因此加入DiskLruCache,虽然在访问速度上逊于Memory Cache,但是速度也是相当可观的。

借鉴Google的做法,我也将缓存做了两份,一份是Memory Cache,使用弱引用的WeakHashMap来控制Bitmap的生命周期,后面会有详细解释。另一份严格来说不能算是缓存,直接将文件存储在SDCard上,避免重复下载。

佛说引用,既非引用,是名引用。

关于引用,或许对于小菜鸟们不是很好理解(我碰到过太多Java都没学好来做Android的,基础很重要!。我使用金刚经的著名三段论来解释它:佛说XX,既非XX,是名XX

这句话什么意思呢?比如佛说大米,可以说它不是大米,只是名字叫做大米罢了。不会因为你为它改名叫做大麦而改变它的本质,你叫它做水,吃到嘴里的还是原来的味道

关于引用,跟这个有着非常相似的共性。引用就相当于实际对象的名字,比如下面的例子:

1 Person p1 =newPerson();
2 Person p2 =null;
3 p2 = p1;
4 p1 =null;

new Person()这个对象的名是p1,而后你将名字改成了p2,对象还是那个对象,不会因为你将p1的大名盖在null的头上而改变它的本质。以上的p1和p2都是引用,它们都不过是名。

在了解到引用的含义后,虚拟机会告诉你,被引用的对象处于可获得(reachable)状态,它是你的好管家,既然你要用它,它就不会回收它。想想如果你正在吃一只烤鸭,人家突然一把抢了过去扔垃圾桶了你什么感觉。

如果在上面的那段程序后面加上p2 = null,Person这个对象就没有任何引用指向它了,垃圾回收器会在不确定的时间进行回收你都把东西扔了,总不能不让人家收破烂吧?)

如果你想继续持有这个对象的引用,希望可以继续访问,但是也允许垃圾回收器进行回收,该怎么办呢(你想减肥,告诉你的好朋友说,如果察觉到你太胖了,就将你嘴里的烤鸭抢去扔了。如果你很饿,身材也不错,你要继续吃。

这个时候,我们需要借助Java提供的软/弱/虚引用。我们平时使用的如p1和p2这样的叫做强引用(StrongReference)。要使垃圾回收器能在内存不够的时候,主动抢下你嘴里的烤鸭,进行回收,需要使用这些:

  • 引用:SoftReference
  • 弱引用:WeakReference
  • 虚引用:PhantomReference

它们按照由强到弱的引用关系排列,虚引用相当于几乎没有引用。文艺青年常说的若即若离用来形容它再恰当不过了

关于这三个引用的具体学习,详见我提供的参考资料。这里只是向你解释为什么使用弱引用可以起到防止Bitmap过多而导致内存紧张的作用

在这里,由于我需要使用Bitmap和名字的key-value对应关系,我使用Java提供的WeakHashMap(String key, Bitmap value),顾名思义,它用来保存WeakReference,并且确保每个key只对应一个值,在内存不够的时候,垃圾回收器会进行回收。当key值索引不到Bitmap,再进行其他的操作。

原理示意图

我将原理画成图,以便大家的理解。主体有三个,分别是UI,缓存模块和数据源。它们之间的关系如下:

UI:请求数据,使用唯一的Key值索引Memory Cache中的Bitmap。

内存缓存:缓存搜索,如果能找到Key值对应的Bitmap,则返回数据。否则执行第三步。

硬盘存储:使用唯一Key值对应的文件名,检索SDCard上的文件。

如果有对应文件,使用BitmapFactory.decode*方法,解码Bitmap并返回数据,同时将数据写入缓存。如果没有对应文件,执行第五步。

下载图片:启动异步线程,从数据源下载数据(Web)。

若下载成功,将数据同时写入硬盘和缓存,并将Bitmap显示在UI中。

总结:这节课除了吐槽,主要的还是原理分析。如果你有更好的缓存方案,欢迎提出。下节课将讲解具体的Memory Cache和FileCache如何实现。

参考资料:

【1】Thinking In Java 4th Chapter 17.12 Hoding References.pdfhttp://vdisk.weibo.com/s/jtqjr

【2】李刚:突破程序员基本功的16课之Java的内存回收.pdfhttp://vdisk.weibo.com/s/jtqik

更多相关文章

  1. Android中适配器getView()原理和ListView加载多个Item.
  2. Android(安卓)模仿QQ登录下拉历史列表
  3. Android中使用POI加载与显示word文档
  4. (转)Activity的详解
  5. 谷安: 微软、苹果与谷歌的专利问题四格漫画,你懂的!
  6. Binder 浅出深入 -- 你真的会用 binder?
  7. android获取web服务器端session并验证登陆 一
  8. 使用Android(安卓)API最佳实践 Retrofit OKHttp GSON
  9. android之Task和Back Stack(回退栈)

随机推荐

  1. [GUIDE][SCRIPT] How to root Android x8
  2. android Spinner的使用
  3. Android(安卓)eclipse 签名打包及遇到的
  4. Android Studio中Android Parcelable cod
  5. Android OpenGL 画简单的三角形
  6. android绘制view的过程(自定义view一)
  7. java android 中的Toast
  8. android bitmap溢出问题完美解决方案
  9. android的popuwindow的使用
  10. Android中的服务(service)详解(一)