Glide的内存缓存有很多文章介绍,一级缓存?二级缓存?但我一直有疑惑,究竟什么样的情况把缓存放进所谓一级缓存容器?什么情况把缓存放进所谓二级缓存容器?又是什么情况需要从一级缓存获取数据?什么情况从二级缓存获取数据?另外,这个所谓一、二级缓存到底有怎样的联系?从设计一个缓存机制的角度讲,为什么要设计两个缓存容器?
被缓存的对象EngineResource
在讨论缓存前先理解究竟缓存的是什么东西,Glide设计中缓存的对象是EngineResource,EngineResource持有Resource
1 |
|
此时设定:加载完毕并且页面没有被销毁。使用Profiler查看此时这个Bitmap的情况,它会被四个引用关联,如图:
- 第一个是sun.misc.Cleaner,这在Bitmap的回收中介绍过,是一个幽灵引用,用于感知java Bitmap被回收后释放native Bitmap内存
- 第二个引用链上有ImageView,一张Bitmap要显示那么它需要被ImageView使用
- 第三个引用链就是Glide主动加上的了,引用链上的ActiveResource$ResourceWeakReference对象是一个弱引用,它指向EngineResource对象,EngineResource对象间接持有Bitmap
- 第4个是SingleRequest,在这里实际上和ImageView是一伙的,它是Glide给ImageView设置的tag
Glide的缓存对象是EngineResource,不是Bitmap,假设一种情况:EngineResource实例没有被任何对象引用了,而图片还在显示中,那么下一次GC时EngineResource实例就会被回收,而Bitmap由于被ImageView引用着,不会被回收。反之同理,如果整个ImageView被detach了,而EngineResource实例还在被Glide引用着,下次GC时ImageView被回收,Bitmap也不会被回收,因为它被EngineResource实例引用着。所以,所谓缓存其实就是使用强引用控制被缓存对象不被回收,或者这里演示的使用弱引用感知缓存对象被回收,然后再做进一步的缓存处理。
缓存容器之ActiveResource
数据结构
ActivityResource中保存缓存的是一个HashMap:
1 | final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>(); |
map的键是EngineKey,值是ResourceWeakReference,它是一个弱引用,指向EngineResource,它的代码如下:
1 | static final class ResourceWeakReference extends WeakReference<EngineResource<?>> { |
这段代码当初我看了很久😭,ResourceWeakReference虽然是一个弱引用对象,但是弱引用是对它关联的EngineResource对象来说的,ResourceWeakReference好歹也是一个对象,而且它被HashMap强引用着,这里又把真正持有Bitmap对象的Resource<?>直接引用,究竟在干什么?其实这就是设计ActiveResource的初衷,待会释放资源小节解释。
放入资源
放入资源的步骤实际就是调用ActiveResource的activate方法,
它创建一个ResourceWeakReference,放进HashMap中,调用栈如下:
1 | at com.bumptech.glide.load.engine.ActiveResources.activate(ActiveResources.java:79) |
在notifyCallbacksOfResult方法里还会通知注册的callback加载完成,这个callback里调用SingleRequest,完成主线任务:通知Target图片加载完成了哦。
还有一种情况是从MemoryCache中命中了EngineResource,也会走这个逻辑,把EngineResource放入ActiveResource,并且从MemoryCache中移除。
复用资源
由于ResourceWeakReference是弱引用,所以真正的Bitmap的回收只受ImageView是否还持有Bitmap,或者说View树是否还引用到该ImageView有关,如果页面存在两个两个一模一样的ImageView,加载的图片都是一模一样的,如下:
1 | //所有参数相同的ImageView |
由于第一个加载已经将EngineResource放入ActiveResource了,
那么第二个加载在EngineJob的loadFromActiveResources中就会命中,所以会直接返回同一个Bitmap给第二个ImageView,达到复用资源的目的。
释放资源
主动释放资源
主动调用Glide.with().clear(target)就是主动释放资源,它会移除ActiveResource中保存的对应的EngineResource,并将资源放进MemoryCache中,调用的时机包括:
- 手动调用Glide.with().clear(target);
- 复用ImageView时Glide发现View的R.id.glide_custom_view_target_tag是自己以前设置的Request,取出来调用clear(imageView)
- 该Glide请求与Activity生命周期关联,关闭页面触发onDestroy,进而触发clear(imageView)
被动释放资源
多数情况下,我们并没主动调用过clear(target),要么是Glide帮我们调用了,要么就是被动释放资源,下面代码就模拟被动释放资源的情况:
1 | ImageView iv = findViewById(R.id.iv); |
参考第一小节的引用链图,那么此时就只有一个ResourceWeakReference引用着EngineResource,这时手动触发GC,EngineResource就会被回收,此时ActiveResource中有一个后台线程正等着EngineResource被回收,后台线程会执行如下代码,小子终于等到你了😏:
1 | void cleanupActiveReference( ResourceWeakReference ref) { |
由于EngineResource已经被回收了,那就直接从activeEngineResources移除和这个EngineResource关联的ResourceWeakReference,等待回收,如果真正保存有Bitmap的Resource>为null就返回,在第一小节代码中解释过一般都是null,至此ActiveResource一般回收逻辑就完了。 但后面的代码总不是多余的吧?什么情况Resource>不会null呢?这需要我们主动配置:
1 |
|
设置 isActiveResourceRetentionAllowed 为true后, ResourceWeakReference 会直接持有真正保存有Bitmap的Resource<>,那么当我们加载完图片时,ResourceWeakReference会强引用到Resource:

如图,多了一个对Resource<>的引用。当 EngineResource 、ImageView 都不再引用Resource<>时,ResourceWeakReference 说不好意思爷不让你回收,它会重新建一个 EngineResource ,然后把它放进 MemoryCache 中。为什么要这样设计?上面的注释说的很清楚,考虑这样的情况:
1 | ImageView iv = findViewById(R.id.iv); |
此时触发GC,Resource<>由于被 ResourceWeakReference 引用着,不会回收,想让加载的资源再缓存一会儿,就把它放进MemoryCache中再缓存一段时间,万一等会儿又有相同的加载避免重复加载。试想如果不设置 isActiveResourceRetentionAllowed 为true,因为GC不是程序控制的,我们不知道Resource<?>究竟被回收没有,就有可能导致重复创建资源。
为什么要ActiveResource
它是对正在使用中的Resource的一种弱引用缓存,一方面,避免一个页面中重复加载相同的Bitmap。另一方面,由于使用Glide时没有clear(Target)的习惯,GC触发时机不对,导致虽然ActiveResource移除了EngineResource,但是Bitmap可能还在内存中,这时相同的Glide加载会重新创建一个新的Bitmap,为了避免这种情况,提供了 isActiveResourceRetentionAllowed开关。
ActiveResource主要逻辑图

缓存容器之MemoryCache
MemoryCache比起ActiveResource简单很多,它的实现类的是LruResourceCache,在上面已经讲过,把资源放进MemoryCache需要调用clear(target),每次加载也会拿着Key去MemoryCache找是否有cache可以用。这种情况一般是重复进入一个页面,destroy时Glide clear(target),立即重新进入时资源还在MemoryCache中,命中复用。