universalimageloader清除缓存中缓存何时释放,占用内存多算泄漏吗

| 漏洞检测 |
| 隐藏捆绑 |
从源代码分析Android-Universal-Image-Loader的缓存处理机制
讲到缓存,平时流水线上的码农一定觉得这是一个高大上的东西。看过网上各种讲缓存原理的文章,总感觉那些文章讲的就是玩具,能用吗?这次我将带你一起看过UIL这个国内外大牛都追捧的图片缓存类库的缓存处理机制。看了UIL中的缓存实现,才发现其实这个东西不难
讲到缓存,平时流水线上的码农一定觉得这是一个高大上的东西。看过网上各种讲缓存原理的文章,总感觉那些文章讲的就是玩具,能用吗?这次我将带你一起看过UIL这个国内外大牛都追捧的图片缓存类库的缓存处理机制。看了UIL中的缓存实现,才发现其实这个东西不难,没有太多的进程调度,没有各种内存读取控制机制、没有各种异常处理。反正UIL中不单代码写的简单,连处理都简单。但是这个类库这么好用,又有这么多人用,那么非常有必要看看他是怎么实现的。先了解UIL中缓存流程的原理图。
原理示意图
& & 主体有三个,分别是UI,缓存模块和数据源(网络)。它们之间的关系如下:
① UI:请求数据,使用唯一的Key值索引Memory Cache中的Bitmap。
② 内存缓存:缓存搜索,如果能找到Key值对应的Bitmap,则返回数据。否则执行第三步。
③ 硬盘存储:使用唯一Key值对应的文件名,检索SDCard上的文件。
④ 如果有对应文件,使用BitmapFactory.decode*方法,解码Bitmap并返回数据,同时将数据写入缓存。如果没有对应文件,执行第五步。
⑤ 下载图片:启动异步线程,从数据源下载数据(Web)。
⑥ 若下载成功,将数据同时写入硬盘和缓存,并将Bitmap显示在UI中。
接下来,我们回顾一下UIL中缓存的配置(具体的见《UNIVERSAL IMAGE LOADER.PART 2》)。重点关注注释部分,我们可以根据自己需要配置内存、磁盘缓存的实现。
File cacheDir = StorageUtils.getCacheDirectory(context,
&UniversalImageLoader/Cache&);
ImageLoaderConfiguration config = new
ImageLoaderConfiguration .Builder(getApplicationContext())
.maxImageWidthForMemoryCache(800)
.maxImageHeightForMemoryCache(480)
.httpConnectTimeout(5000)
.httpReadTimeout(20000)
.threadPoolSize(5)
.threadPriority(Thread.MIN_PRIORITY + 3)
.denyCacheImageMultipleSizesInMemory()
.memoryCache(new UsingFreqLimitedCache(2000000)) // 你可以传入自己的内存缓存
.discCache(new UnlimitedDiscCache(cacheDir)) // 你可以传入自己的磁盘缓存
.defaultDisplayImageOptions(DisplayImageOptions.createSimple())
UIL中的内存缓存策略
1. 只使用的是强引用缓存&
LruMemoryCache(这个类就是这个开源框架默认的内存缓存类,缓存的是bitmap的强引用,下面我会从源码上面这个类)
&2.使用强引用和弱引用相结合的缓存有
&UsingFreqLimitedMemoryCache(如果缓存的图片总量超过限定值,先删除使用频率最小的bitmap)
LRULimitedMemoryCache(这个也是使用的lru算法,和LruMemoryCache不同的是,他缓存的是bitmap的弱引用)
FIFOLimitedMemoryCache(先进先出的缓存策略,当超过设定值,先删除最先加入缓存的bitmap)
LargestLimitedMemoryCache(当超过缓存限定值,先删除最大的bitmap对象)
LimitedAgeMemoryCache(当 bitmap加入缓存中的时间超过我们设定的值,将其删除)
&3.只使用弱引用缓存
&WeakMemoryCache(这个类缓存bitmap的总大小没有限制,唯一不足的地方就是不稳定,缓存的图片容易被回收掉)
我们直接选择UIL中的默认配置缓存策略进行。
ImageLoaderConfiguration config = ImageLoaderConfiguration.createDefault(context);
ImageLoaderConfiguration.createDefault(&)这个方法最后是调用Builder.build()方法创建默认的配置参数的。默认的内存缓存实现是LruMemoryCache,磁盘缓存是UnlimitedDiscCache。
LruMemoryCache解析
LruMemoryCache:一种使用强引用来保存有数量限制的Bitmap的cache(在空间有限的情况,保留最近使用过的Bitmap)。每次Bitmap被访问时,它就被移动到一个队列的头部。当Bitmap被添加到一个空间已满的cache时,在队列末尾的Bitmap会被挤出去并变成适合被GC回收的状态。&
注意:这个cache只使用强引用来保存Bitmap。
LruMemoryCache实现MemoryCache,而MemoryCache继承自MemoryCacheAware。
public interface MemoryCache extends MemoryCacheAware&String, Bitmap&
下面给出继承关系图
Android-Universal-Image-Loader.LruMemoryCache
LruMemoryCache.get(&)
我相信接下去你看到这段代码的时候会跟我一样惊讶于代码的简单,代码中除了异常判断,就是利用synchronized进行同步控制。
& & &* Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head
& & &* of the queue. This returns null if a Bitmap is not cached.
& & @Override
& & public final Bitmap get(String key) {
& & & & if (key == null) {
& & & & & & throw new NullPointerException(&key == null&);
& & & & synchronized (this) {
& & & & & & return map.get(key);
我们会好奇,这不是就简简单单将Bitmap从map中取出来吗?但LruMemoryCache声称保留在空间有限的情况下保留最近使用过的Bitmap。不急,让我们细细观察一下map。他是一个LinkedHashMap&String, Bitmap&型的对象。
LinkedHashMap中的get()方法不仅返回所匹配的值,并且在返回前还会将所匹配的key对应的entry调整在列表中的顺序(LinkedHashMap使用双链表来保存数据),让它处于列表的最后。当然,这种情况必须是在LinkedHashMap中accessOrder==true的情况下才生效的,反之就是get()方法不会改变被匹配的key对应的entry在列表中的位置。
&1 @Override public V get(Object key) {
&2 & & & & /*
&3 & & & & &* This method is overridden to eliminate the need for a polymorphic
&4 & & & & &* invocation in superclass at the expense of code duplication.
&5 & & & & &*/
&6 & & & & if (key == null) {
&7 & & & & & & HashMapEntry&K, V& e = entryForNullK
&8 & & & & & & if (e == null)
&9 & & & & & & & &
10 & & & & & & if (accessOrder)
11 & & & & & & & & makeTail((LinkedEntry&K, V&) e);
12 & & & & & & return e.
13 & & & & }
15 & & & & // Replace with Collections.secondaryHash when the VM is fast enough ().
16 & & & & int hash = secondaryHash(key);
17 & & & & HashMapEntry&K, V&[] tab =
18 & & & & for (HashMapEntry&K, V& e = tab[hash & (tab.length - 1)];
19 & & & & & & & & e != e = e.next) {
20 & & & & & & K eKey = e.
21 & & & & & & if (eKey == key || (e.hash == hash && key.equals(eKey))) {
22 & & & & & & & & if (accessOrder)
23 & & & & & & & & & & makeTail((LinkedEntry&K, V&) e);
24 & & & & & & & & return e.
25 & & & & & & }
26 & & & & }
27 & & & &
代码第11行的makeTail()就是调整entry在列表中的位置,其实就是双向链表的调整。它判断accessOrder
(责任编辑:幽灵学院)
------分隔线----------------------------
今天有空复习了一下Android中AIDL的使用,由于平时开发中使...
在之前的文章深入探究了Handler,我们知道了Android的消息机...
?之前一段时间,我都在研究Android自定义View的相关知识,随...
什么是ViewPager,刚一听到这个词,我们可能感觉很奇怪,但...
Only the original thread that created a view hierarchy c...
在这里写出我们项目中常用的两种弹窗方式,底部弹窗以及中间...
工作日:9:00-21:00
周 六:9:00-18:00
&&扫一扫关注幽灵学院Android 优化(12)
公司正在做的项目使用到了ImageLoader来加载大量图片,我也是第一次使用,就拿来直接用了。写完的代码运行很正常的加载图片,并没有发现什么问题。但是软件拿给测试部门测试的时候发现了问题。当多次打开软件中一个游览大图片的Activity(每次游览的图片都不一样)后,这时在这个Activity中点击返回后,屏幕会突然变黑,然后回到了软件的MainActivity,并没有回到上一级Activity,甚至连之间打开的多个Activity都没有返回。当自己亲自测试了这个Bug后,想了一下问题怀疑可能是重启了这个应用。但那个游览大图片的Activity开始打开的几次都可以正常的回到上一级Activity,为什么再多打开几次就不能正常回退了呢!这时候我就开始看LogCat打出的日志,看看有没有发生什么异常。发现ImageLoader产生了OOM的Bug。如下图:
然后上网上查了一下ImageLoader引起内存泄露的问题,发现有很多人遇到,但说到解决的办法的,并没有很好的答案。大部分都是说ImageLoaderConfiguration.Builder(context)和DisplayImageOptions.Builder()里面的配置问题,但并不能解决这个问题。还有人说换一个图片加载的框架,像Universal-Image-Loader(UIL)和Google的Volley框架,我尝试换了Universal-Image-Loader(UIL)这个,但发现还是有这个问题。而且项目已经到后期,换一个框架改动比较大,不太现实,就没有尝试Volley框架。还有人说是应为使用了cacheInMemory(true)和cacheOnDisc(true)这两个属性导致的,但是我将他们设为false还是会引发OOM,所以就自己用工具分析。&
既然产生了OOM,我们就要分析内存,看看具体是什么原因导致的。正好最近刚刚开始使用&Studio工具(使用过后真的会上瘾,发现很多地方都比eclipse强大),在使用AndroidStudio的Memory工具观察的时候发现了问题,在我多次打开这个Activity的时候,发现Memory在一直的增长,每次Activity退出后Memory也没有下降。
就这样当我多次游览不同的图片,多次打开这个Activity后,这个Memory就一直的增长,当增长到120+M的时候应用突然挂掉,之后又自动重启。发现了这个现象我们就可以借助AndroidStudio的强大工具来分析导致这个问题的原因。在Memory窗口的左边有四个按钮,分别是:&
Enabled(蓝色的开关):就是一个正常的开关功能&
Initiate GC(橙色小卡车):就是手动调用GC,我们在抓内存前,一定要手动点击 Initiate GC按钮手动触发GC,这样抓到的内存使用情况就是不包括Unreachable对象的(Unreachable指的是可以被垃圾回收器回收的对象,但是由于没有GC发生,所以没有释放,这时抓的内存使用中的Unreachable就是这些对象)&
Dump&&Heap(紫色带向下的箭头):获取hprof文件(hprof文件是我们使用MAT工具分析内存时使用的文件),但这里直接产生的文件MAT还不能直接使用,需用转换成标准的hprof文件。可以使用AndroidStudio转换或者用hprof-conv命令转化,具体不详细介绍,网上可以查到。&
Start Allocation Tracking(紫色带圆圈):开始分配追踪,第一次点击可以指定追踪内存的开始位置,第二次点击可以结束追踪的位置。这样我们截取了一段要分析的内存,等待几秒钟AndroidStudio会给我们打开一个Allocation视图(感觉和MAT工具差不多,不过MAT工具更加强大,我们也可以获取hprof文件,使用MAT来分析)如下图:
我截取了这段内存,接下来我们就看Allocation视图来分析。
通过视图中的内容我们首先看到23号线程它占用了百分之40的内存,那么我们就点开它看一下,每次都只要点开其中内存占用最大的就可以。
到划红线的那个位置就可以了,这里已经到了的android.graphics源码的层次,所以我们不需要再向下看了。我们看一下红线上面的ImageLoader里的那个decodeStream方法:&
它里面就是调用了BitmapFactory.decoceStream()方法然后返回了一个Bitmap对象。基本上已经可以确定占用如此多内存的就是Bitmap对象,那么我们只要将这个Bitmap释放掉就可以了。那么就接着分析看看在什么地方释放,我们看一下最上面的ImageLoader.core包下的LoadAndDisplayImageTask类的run()方法,发现里面有一个Bitmap对象,他是调用了内部tryLoadBitmap()方法给赋值的,而这个方法最后其实就是我们上面看到的调用的decodeStream方法返回的Bitmap。应为这之间的Bitmap都是方法里的局部变量所以我们不用考虑这些Bitmap的释放,那就接着向下看,发现这个Bitmap被传到了DisplayBitmapTask这个类中,那么我们再进入到这个类中看发现了一个惊喜:
这个对象被传到了DisplayBitmapTask的成员变量中,DisplayBitmapTask本身也实现了Runnable接口,那么我就想是不是应为DisplayBitmapTask这个类没有及时回收导致的Bitmap成员变量占用内存的问题,所以我就在run()方法跑完之后,让Bitmap = null。然后又将程序运行了一遍,但是发现根本没有解决这个问题。这时候又重头想了一下整个过程,发现我的操作是不对的,虽然我成功将DisplayBitmapTask中的Bitmap成员变量置为了空,但是他只是真正的Bitmap内存的一个引用,我并没有将真正的Bitmap内存空间所释放,只是将他的一个引用给释放了。通过网上查询发现,Bitmap会保存在C层的内存中,如果我们想要释放他在C层中的内存,可以调用Bitmap的recycle()方法,从代码中看,这个方法本身会调用Bitmap.cpp中的Bitmap_recycle方法,这个是native方法。这样就可以通知底层将C层中的Bitmap内存释放掉,而且还会将一些相关的引用计数置0。但是并不会立即释放掉,这要看系统。
一下子完美解决。既然查到了原因,那就开始实践,我将Bitmap = null这句话改为Bitmap.recycle()。好了,接下来我再次运行,发现这回应用直接就崩了,汗!但是可以接受,只要能在Logcat中抓到Log,一切问题都好解决。看了一下log发现下面这个Error:
通过字面理解的意思就是,我们的View去显示了已经recycle的Bitmap。仔细想一想确实是,我们在ImageLoader中的一个线程的run方法中直接将Bitmap释放掉了,那我们的应用岂不是直接显示一个空的Bitmap,这当然会引起错误。既然不能在这里释放掉Bitmap,那我们应该在什么地方释放呢?那一定是在我们的View不再使用这个Bitmap的时候调用,想一想,那我们只要在Activity或者fragment的onDestory()方法中释放了不就可以了吗?但这时又有一个问题产生,我们该如何在上层应用获取到这个View用到的Bitmap的对象呢?想了半天发现好像只能通过改写ImageLoader的源码来想办法将这个对象会传到上层应用。正当我在ImageLoader中寻找在哪里写这个回调方法时,突然我看到了这段代码:
发现其实ImageLoader的源码中已经为我们写好了这个回调监听器接口,而且在ImageLoader的displayImage方法中,也重载了一个提供了传递ImageLoaderListener实现对象的方法。这样我们只需要实现ImageLoaderListener这个接口,并实现他的onLoadingComplete方法,在这个方法中对Bitmap做处理就可以了。
我们只需要在Activity或Fragment的onDestory()方法中再调用cleanBitmapList()方法就可以了。通过上面的更改我再次运行应用,发现真的解决了这个问题,我的应用内存不再会是一直的上升,而是上升之后又会下降。如图:
注:这个方式比较适合cacheInMemory(false)和cacheOnDisc(false)的情况写,应为如果你将这两个设置为true时,那么下次再次加载图片时他会从内存和硬盘中去加载图片,这两个地方也持有Bitmap的引用,这时就会再次出现trying to use a recycled bitmap 这个错误。当然你也是可以设置为true的,只要在清除Bitmap(也就是我们这里调用cleanBitmapList)的地方再加上mImageLoader.clearDiscCache()和mImageLoader.clearMemoryCache()这两句话就可以。
好了,整个ImageLoader引起的OOM问题额分析与解决的过程就是这些,希望能对需要的人有帮助,通过这个问题我也学会到,遇到问题最好的解决办法就是我们要从最根本源码的角度去分析,这样既能学到很多东西,又可以解决困难问题。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:32555次
积分:1068
积分:1068
排名:千里之外
原创:46篇
转载:185篇
(61)(32)(4)(6)(16)(6)(3)(34)(8)(35)(1)(30)}

我要回帖

更多关于 imageloader https 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信