本文基于Glide 4.11.0
参考文章:Glide 源码分析解读-缓存模块-基于最新版Glide 4.9.0
注意:由于版本差异问题,本文有些部分与参考文章有差异。
缓存模块是Glide中非常重要的部分,Glide图片加载的高效性,几乎有一半功劳都在这里了。
一般来说,Glide有三级缓存,就是内存缓存、磁盘缓存和网络缓存。
先来看缓存流程图,如下:
1 | graph TD; |
内存缓存
内存缓存主要靠三个部分组成:ActiveResources、MemoryCache和BitmapPool。
ActiveResources
ActiveResources表示当前正在活动中的资源。ActiveResources通过一个Map<Key, ResourceWeakReference>
来保存活动中的资源,其中的ResourceWeakReference就是资源数据,在构建这个ResourceWeakReference的时候必须传入一个ReferenceQueue用来检测资源是否被回收。
Q1:如何探知WeakReference中的值被回收了呢?
1
2 ReferenceQueue queue = ...;
WeakReference wr = new WeakReference(value, queue);当构建WeakReference的时候,如果传入了queue参数,则在value被回收的时候,wr会被加入到queue中去,这样,通过检测queue中是否有值,就可以探知value是否被回收了。
那么,在何时去探知ReferenceQueue中的值呢?我们查看ActiveResources的关键代码:
1 | /*构造方法中,通过monitorClearedResourcesExecutor执行了cleanReferenceQueue()方法。 |
我们通过代码可以看出,cleanReferenceQueue
是一个靠isShutdown
变量控制的死循环方法,这个方法执行在一个优先级为THREAD_PRIORITY_BACKGROUND
的线程上。
Q2:那么,既然是死循环方法,会不会过多的占用CPU资源呢?
其实不会的,因为ReferenceQueue#remove是一个阻塞式的方法,如果没有元素可以被remove,则等待至有元素可以remove的时候,等待期间释放CPU。
注意:此处与参考文章中的说法不同,这是因为版本差异。查看Glide update log hsitory,可以看出出于避免在主线程做清理的原因,将清理任务放在了后台线程,而不是放在IdleHandler中。
那么被回收了的资源去哪里了呢?
我们查看cleanupActiveReference
方法,得知,通过ResourceListener#onResourceReleased回调,交给了Engine来处理,我们查看Engine的onResourceReleased方法。
1 |
|
从这里我们发现,这里出现了两种情况:
- 如果资源是MemoryCacheable的,则缓存在MemoryCache;
- 如果资源不是MemoryCacheable的,则交给ResourceRecycler调用Resource的recycle()方法来回收,如果此Resource为BitmapResource,则会将Bitmap回收到BitmapPool中去。
在开始MemoryCache和BitmapPool前,需要先了解一下MemorySizeCalculator这个类,这个类是用来计算 BitmapPool 、ArrayPool 以及 MemoryCache 大小的。
MemoryCache
MemoryCache的具体实现类是LruResourceCache,而实际的逻辑方法,都在其父类LruCache中,以put方法为例。
1 |
|
当有一个新的item被put进去以后,会替换出一个老的值old,如果old非为空,则需要将当前容量减去old的大小,如果old并非新的item,则需要通过onItemEvicted进行回调,通知有老值被“驱逐”了。最后还要执行一次evict方法,按照LRU算法,将超出maxSize的item“驱逐”出去,以确保在maxSize范围内。
1 | protected synchronized void trimToSize(long size) { |
被**”驱逐”*的值去哪里了呢?我们查看MemoryCache类的源码,可以知道是通过ResourceRemovedListener回调给了Engine,在Engine中我们查看onResourceRemoved*方法。
1 |
|
我们可以看到,这里同样是通过resourceRecycler进行了回收。在这里,则是交给resource自己的recycle()方法来处理,比如,BitmapResource是交给了BitmapPool进行处理。
BitmapPool
这里是专门用来存放被回收的Bitmap的,其中BitmapDrawableResource、BitmapResource都持有一个BitmapPool变量,在执行recycle()方法时候,调用*BitmapPool#put()*方法。我们来看一下这个BitmapPool的默认实现类LruBitmapPool的方法实现。
1 |
|
这里我们可以看出,当Bitmap在三种情况下是不会被BitmapPool缓存起来的:
- 这个bitmap是非mutable的,也就是说是不允许被复用的;
- 这一个bitmap的字节数大小已经超过了可以容纳的总大小;
- BitmapPool中不允许的Config类型。
在这种情况,bitmap就被直接recycle掉,而不是放入缓存等待下次使用。
如果不满足这三种情况,则会被strategy缓存起来,等待下次使用。
我们再看LruBitmapPool#get()方法。
1 |
|
我们可以看到,当能够查询到符合条件的Bitmap的时候,会先通过eraseColor方法,将其变成透明图片,然后再交给调用者来使用;如果查询不到,则创建一个新图交给调用者来使用。
LruBitmapPool的LruPoolStrategy变量,在KITKAT以及以上,是SizeConfigStrategy,在以下是AttributeStrategy,这是因为在KITKAT版本以下,Bitmap的复用需要尺寸的严格匹配,但是KITKAT及以上没有这个问题,只要被复用的图片尺寸比目标尺寸大就可以。
ArrayPool
ArrayPool主要用在ThumbnailStreamOpener和ByteBufferGifDecoder中,具体的实现类为LruArrayPool。
在LruArrayPool中,通过groupedMap来缓存数据,而缓存数据的byte字节数是通过ArrayAdapterInterface来计算的,ArrayAdapterInterface是一个接口,实现类有两个:IntegerArrayAdapter和ByteArrayAdapter,分别对应缓存int[].class和byte[].class。
StreamGifDecoder和StreamBitmapDecoder都有一个ArrayPool成员。解码过程中需要用到byte[],但不是直接new byte[],而是调用ArrayPool.get()
从对象池中拿,用完了归还。
DiskCache
在上一章[Glide源码分析与自我实现(一)——数据加载主流程](/源码分析系列/Glide源码分析与自我实现2.md)中,提到过数据加载的主流程,其中一个非常重要的类是 DecodeJob,在这个类的getNextGenerator
方法中,返回的SourceGenerator会用来加载远程数据,但是这个方法不止返回这一个DataFetcherGenerator类,这是一个通过条件判断,返回不同DataFetcherGenerator类的方法。
1 | private DataFetcherGenerator getNextGenerator() { |
实际上,这是依次递进的有限状态机设计模式,当一个获取数据请求到来时候,此时是默认状态INITIALIZE,然后通过getNextStage
方法判断下一个状态是什么,再按照新的状态获取DataFetcherGenerator,然后随着任务的执行,不断改变状态。
1 | private Stage getNextStage(Stage current) { |
其状态变更顺序为INITIALIZE -> RESOURCE_CACHE -> DATA_CACHE -> SOURCE,代表着ResourceCacheGenerator、DataCacheGenerator和SourceGenerator,当从ResourceCahce中拿不到数据,则向DataCacheGenerator请求数据,如果还是拿不到,则通过SourceGenerator去请求数据了。
在这个过程中,SourceGenerator向DiskCache中写入数据,ResourceCacheGenerator和DataCacheGenerator从DiskCache中读取数据。
1 | class ResourceCacheGenerator implements DataFetcherGenerator, |
DiskCache的默认实现类是DiskLruCacheWrapper,其内部通过DiskLruCache来管理磁盘缓存。
总结
到现在,Glide主要部分已经分析的差不多了,实际上这个优秀的框架可挖的地方还有很多,比如通过[APT来实现很好的扩展](/源码分析系列/Glide源码分析与自我实现3.md),框架中涉及多种涉及模式等。
其中涉及到的涉及模式,比如无处不在的构建者模式和工厂模式,DecodeJob中的有限状态机模式,还有BitmapPool和ArrayPool中的享元模式,DiskLruCacheWrapper中的代理模式等。
参考文章
Author: boybeak