【构建Android缓存模块】(一)吐槽与原理分析
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缓存起来,缓存做了两份:LruCache和DiskLruCache,分别是内存缓存和硬盘缓存。此外两个至关重要的类是:
BitmapWorkerTask(ImageView imageView)
AsyncDrawable extends BitmapDrawable
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 |
Personnew Person(); |
2 |
Personnull; |
3 |
p2 |
4 |
p1null; |
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:请求数据,使用唯一的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.pdf http://vdisk.weibo.com/s/jtqjr
【2】李刚:突破程序员基本功的16课之Java的内存回收.pdf http://vdisk.weibo.com/s/jtqik
from:http://blog.csdn.net/floodingfire/article/details/8247021
【构建Android缓存模块】(一)吐槽与原理分析的更多相关文章
- android 脱壳 之 dvmDexFileOpenPartial断点脱壳原理分析
android 脱壳 之 dvmDexFileOpenPartial断点脱壳原理分析 导语: 笔者主要研究方向是网络通信协议的加密解密, 对应用程序加固脱壳技术很少研究, 脱壳壳经历更是经历少之甚少. ...
- Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析[转]
前面在介绍Android系统的开机画面时提到,Android设备的显示屏被抽象为一个帧缓冲区,而Android系统中的SurfaceFlinger服务就是通过向这个帧缓冲区写入内容来绘制应用程序的用户 ...
- Android中Input型输入设备驱动原理分析(一)
转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...
- Android中Input型输入设备驱动原理分析<一>
话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反正这个是没变的,在android的底层开发中对于Linux的基本驱动程序设计还是没变的,当然Android底层机制也 ...
- Android控件TextView的实现原理分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8636153 在前面一个系列的文章中,我们以窗口 ...
- 从Redis分布式缓存实战入手到底层原理分析、面面俱到覆盖大厂面试考点
概述 官方说明 Redis官网 https://redis.io/ 最新版本6.2.6 Redis中文官网 http://www.redis.cn/ 不过中文官网的同步更新维护相对要滞后不少时间,但对 ...
- Android FM模块学习之四源码分析(3)
接着看FM模块的其他几个次要的类的源码.这样来看FM上层的东西不是太多. 请看android\vendor\qcom\opensource\fm\fmapp2\src\com\caf\fmradio\ ...
- Android大图片裁剪终极解决方案 原理分析
约几个月前,我正为公司的APP在Android手机上实现拍照截图而烦恼不已. 上网搜索,确实有不少的例子,大多都是抄来抄去,而且水平多半处于demo的样子,可以用来讲解知识点,但是一碰到实际项目,就漏 ...
- android脱壳之DexExtractor原理分析[zhuan]
http://www.cnblogs.com/jiaoxiake/p/6818786.html内容如下 导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的 ...
随机推荐
- 设计模式-迭代器模式(Iterator Pattern)
本文由@呆代待殆原创,转载请注明出处:http://www.cnblogs.com/coffeeSS/ 迭代器模式简介 迭代器相信大部分人都不陌生,java/c++等很多编程语言的容器类都支持迭代器操 ...
- SNOI2017(BZOJ5015~5018)泛做
T1:礼物 想错方向了,实际上很简单. 我想的是:显然题目求的是$\sum_{i=1}^{n} i^{k}2^{i}$,然后或许可以通过化式子变成与n无关的复杂度? 然后就不停往斯特林数反演和下降幂的 ...
- 【博弈论】【SG函数】【线段树】Petrozavodsk Summer Training Camp 2016 Day 9: AtCoder Japanese Problems Selection, Thursday, September 1, 2016 Problem H. Cups and Beans
一开始有n个杯子,每个杯子里有一些豆子,两个人轮流操作,每次只能将一个豆子移动到其所在杯子之前的某个杯子里,不过可以移动到的范围只有一段区间.问你是否先手必胜. 一个杯子里的豆子全都等价的,因为sg函 ...
- 【递推】【DFS】【枚举】Gym - 101246C - Explode 'Em All
网格里放了一些石块,一个炸弹能炸开其所在的行和列.问炸光石块至少要几个炸弹. 枚举不炸开的行数,则可以得出还要炸开几列. 为了不让复杂度爆炸,需要两个优化. 先是递推预处理出f(i)表示i的二进制位中 ...
- 【BFS】bzoj2252 [2010Beijing wc]矩阵距离
要注意一开始将所有为'1'的点入队,然后通过一次BFS去更新所有点的距离,直到无法更新为止. #include<cstdio> #include<queue> #include ...
- 【状态压缩DP】NOIP2005-river过河
[问题描述] 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看 ...
- python基础之序列化 time random os
序列化与反序列化 json pickle 1.什么是序列化与反序列化? 序列化就是将内存中的数据结构转成一种中间格式储存到硬盘或者基于网络传输 反序列化是网络,硬盘将被序列化的对象重新读到内存 2. ...
- GCC,LLVM,Clang编译器对比
http://www.cnblogs.com/qoakzmxncb/archive/2013/04/18/3029105.html 在XCode中,我们经常会看到这些编译选项(如下图),有些人可能 ...
- How do I find what queries were executing in a SQL memory dump?-----stack
https://blogs.msdn.microsoft.com/askjay/2010/10/03/how-do-i-find-what-queries-were-executing-in-a-s ...
- Java并发包之闭锁/栅栏/信号量
二.同步工具类详解 1.Semaphore信号量:跟锁机制存在一定的相似性,semaphore也是一种锁机制,所不同的是,reentrantLock是只允许一个线程获得锁,而信号量持有多个许可(per ...