Feb 18, 2020
Glide的缓存策略源码分析
接着我们先看看第一步中的load
方法:
从下面的代码可以看到,先会从缓存中获取资源,如果不存在,才会新开线程从硬盘获取或者从其他远端(文件、网络等等)加载新资源。
内存缓存
内存缓存的设置也有两级,这里就是引言中使用的弱引用+Lru的方式:
activeResource
中的逻辑很简单清晰,内部维护了一个HashMap
用键值对的形式保存了key
和WeakReference
,这里直接去获取,当存在就返回:
如果弱引用中不存在了,就从Lru
中去获取,从方法的调用上看也是一层层的获取,当缓存存在时,从Lru
中取出来,并重新加入弱引用中。
到这里为止,都是正常的流程。但我们知道,弱引用会在下一次GC
时被回收,如果被回收了又没有加入Lru
不就炸了么?对弱引用的使用不太了解的同学也许会存在这样的疑问,Reference
有一个构造方法可以传入一个ReferenceQueue
,这个队列的作用就是用于检测对象本身可达性发生变化时,通过Map来反向获取指向对象的包装引用(即Reference
对象),将其放入到队列,来进行后续的操作,这里有一篇关于引用队列的介绍。
正如上面的介绍一样,Glide
的内部也是通过引用队列来实现弱引用对象被回收时,将其放入Lru
中。
流程如下:
- 当我们第一次加载成功后,会创建弱引用对象来缓存它。
- 创建弱引用对象时也会传入弱引用队列。
- 同时这个对象资源在初始时会新开一个线程去无限循环的检测弱引用队列。
- 弱引用队列的
remove()
方法是个阻塞方法,当队列中存在元素时才会执行,所以当gc()
回收弱引用对象后,队列中就有值了 - 这个时候无限循环的检测中就会拿到刚刚回收的弱引用对象。就会将该对象放到
lru
中去缓存。 - 另一边是如果使用的资源被主动释放了,
Lru
内部是维护了一个引用计数acquired
的数字,当这个数子为0时就代表没有引用了,也会缓存到Lru
中去。
看看具体的实现:
首先ActiveResources
的构造方法中,会生成一个子线程执行检测任务,随时检测引用队列的remove()
方法(这是一个阻塞方法,队列没有值的时候会一直阻塞),当还没有引用别回收时,这个检测的线程就会因为阻塞而休眠,而一旦出现了被回收的对象,就会被引用队列唤醒,执行remove
方法的后续。
在资源首次被添加时,会去生成一个弱引用的对象,然后在上面的检测中检测它的回收
在cleanupActiveReference
中,引用的资源还在时,就会新建一个resource
然后通知engine
把这个新的存到lru
中,这样就完成了一次弱引用到lru
的转换了
这里还有一个细节就是每一次获取成功后都会调用cached.acquire()
这个方法,这个方法内会自增一个计算的变量,用于统计资源的使用,每次release
时都会自减,当这个值为0时说明没有使用他的地方了,同样的也会调用listener.onResourceReleased
方法来将该变量存到Lru
中。
硬盘缓存
硬盘缓存的使用是DiskLruCache
,Glide
也提供了几种缓存的策略如下
DiskCacheStrategy.NONE: 表示不缓存任何内容。
DiskCacheStrategy.SOURCE: 表示只缓存原始图片。
DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)。
DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
在最开始的图中,waitForExistingOrStartNewJob
这个命名就基本说明了接下来的流程,先从本地的硬盘缓存中查找,如果没有就从远端资源获取。只不过这个地方的源码稍微有点绕。
engine
在waitForExistingOrStartNewJob
方法中构建了DecodeJob
对象,并且开启了新的线程去执行它(这个对象实现了runnable
)。
根据上面不同中的runReason
会生成不同的stage
,然后生成不同的generator
,主要有三种:
- ResourceCacheGenerator:这个类中会去寻找经过处理之后的资源
- DataCacheGenerator:这个类中会去寻找原始的资源
- SourceGenerator:这个类就是去远端获取资源
在glide
可以同时缓存原始的资源和经过转换后的资源,当然这都是根据你设置的缓存策略来实现了。在DecodeJob
中会去调用他们的startNext()
方法,接下来就分别看看上面两个缓存的实现:
这两个里面的实现大体逻辑都差不多,只是对于的key
不一样,而这个helper.getDiskCache
最后拿到的就是DiskLruCacheWrapper
的实例,在他的getDiskCache
中去获取磁盘的缓存:
到这里,会通过之前设置好的callback
一步步调用到engine
中,返回这个磁盘缓存的资源。