前言
Glide作为一个强大的图片加载框架,必然有着一套完整且优秀的缓存机制。本文将基于Glide-4.9.0版本,对Glide的缓存机制进行源码分析。
缓存相关的类
下面先介绍一下Glide缓存涉及到的一些类:
ActiveResources
ActiveResources是第一级缓存,表示当前正在活动的资源。当资源加载成功,或者通过其他缓存获得资源后都会将其添加到ActiveResources中。当资源被gc后,就会将其移除出ActiveResources。
ActiveResources通过Map来存储数据,数据保存在WeakReference中1
2//ResourceWeakReference继承于WeakReference<EngineResource<?>>
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
此外,还有一个引用队列1
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
当一个弱引用对象被gc掉之后,其对应的Reference对象会加入到引用队列中。从引用队列获取到被gc掉的弱引用对象后,就可以将该对象从ActiveResources中删除。
MemoryCache
MemoryCache(内存缓存)是第二级缓存,如果ActiveResources没有获取到资源就从这里获取。
它的实现类是LruResourceCache,继承于LruCahce。主要的实现在LruCache中,LruCache使用LRU算法进行缓存。它的内部使用LinkedHashMap存储数据,LinkedHashMap内部可以设置为按照访问顺序进行排序,最近最少访问的在前面,这很适合LRU算法,在清除缓存的时候,只要从前面的元素开始删除,一直删除到满足容量即可。
LruCache
看下LruCache中存取数据的几个方法:
get方法获取指定数据1
2
3
4 public synchronized Y get(@NonNull T key) {
//获取缓存数据
return cache.get(key);
}
remove方法删除指定数据1
2
3
4
5
6
7
8
9 public synchronized Y remove(@NonNull T key) {
//从缓存中删除指定数据
final Y value = cache.remove(key);
//如果删除成功,就更新当前缓存容量,并返回删除数据
if (value != null) {
currentSize -= getSize(value);
}
return value;
}
put方法添加缓存数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 public synchronized Y put(@NonNull T key, @Nullable Y item) {
//获取资源的大小
final int itemSize = getSize(item);
//资源大小超出容量限制,不缓存该资源,并回调该资源的移除
if (itemSize >= maxSize) {
onItemEvicted(key, item);
return null;
}
//如果资源不为空,更新当前缓存容量
if (item != null) {
currentSize += itemSize;
}
//将当前资源添加进缓存中
final Y old = cache.put(key, item);
//如果该key之前有其他资源,更新当前缓存容量,并回调该资源的移除
if (old != null) {
currentSize -= getSize(old);
if (!old.equals(item)) {
onItemEvicted(key, old);
}
}
//从缓存中删除最近最少使用的数据,直到满足指定容量
evict();
return old;
}
这几个方法还是比较好理解的,代码中都有注释。
MemorySizeCalculator
MemoryCache在创建Glide实例时初始化,内存缓存的初始化容量从MemorySizeCalculator中获得,代码如下:1
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
getMemoryCacheSize方法如下:1
2
3public int getMemoryCacheSize() {
return memoryCacheSize;
}
其中memoryCacheSize在MemorySizeCalculator初始化时指定,MemorySizeCalculator的初始化也是发生在Glide实例创建时:1
2
3if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
这里采用了Builder模式,在其内部类Builder的build方法中调用了MemorySizeCalculator的构造方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
//ArrayPool的容量默认为4MB,低内存设备为2MB
arrayPoolSize =
isLowMemoryDevice(builder.activityManager)
? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
: builder.arrayPoolSizeBytes;
//最大容量为:当前进程可用内存 * 0.4;对低内存设备,为当前进程可以内存 * 0.33
int maxSize =
getMaxSize(
builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);
//计算一张大小为屏幕大小,格式为ARGB_8888的图片占用的内存大小(包括BitmapPool和MemoryCache)
int widthPixels = builder.screenDimensions.getWidthPixels();
int heightPixels = builder.screenDimensions.getHeightPixels();
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
//去掉ArrayPool占用的内存后,剩余的内存大小
int availableSize = maxSize - arrayPoolSize;
//BitmapPool和MemoryCache的内存相加不超过可用内存大小
if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
memoryCacheSize = targetMemoryCacheSize;
bitmapPoolSize = targetBitmapPoolSize;
}
//超出限制的话,两者按照比例平分可用大小
else {
float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
}
}
可以看到,构造方法中指定了 BitmapPool 和 MemoryCache 可用的内存大小。 BitmapPool 是用来复用 Bitmap,从而避免重复创建 Bitmap 而带来的内存浪费。
小结一下计算BitmapPool和MemoryCache可用内存的步骤:
- 设置ArrayPool的容量,默认为4MB,低内存设备为2MB(安卓版本低于4.4默认为低内存,其它版本由系统判断)
- 设置最大容量,默认为当前进程可用内存 0.4,低内存设备 0.3
- 设置可用内存容量为最大容量减去ArrayPool的容量
- 计算一张大小为屏幕大小,格式为ARGB_8888的图片占用的内存大小(包括BitmapPool和MemoryCache),如果两者内存相加不超过可用容量,那么计算得出的内存大小即为各自的可用内存。如果相加后超出限制的话,两者按照比例平分可用内存。
过程分析
在执行Request的时候,会调用SingleRequest的onSizeReady方法进行加载数据:
SingleRequest#onSizeReady
1 |
|
在真正加载资源前,将Request的状态变为RUNNING。之后调用Engine的load方法真正加载资源:
Engine#load
该方法首先生成缓存key,该key是一个EngineKey对象,由传入的多个参数生成。1
2
3 //生成缓存key(该key是一个EngineKey对象)
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
ActiveResources缓存
接着根据该缓存key获取缓存资源,首先从ActiveResources中获取缓存:1
2
3
4
5
6
7
8//从ActiveResources(弱引用)中获取缓存
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
//获取到缓存资源,回调onResourceReady方法
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
loadFromActiveResources方法:1
2
3
4
5
6
7
8
9
10
11
12
13 private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
//获取缓存资源
EngineResource<?> active = activeResources.get(key);
//如果获取到缓存资源,就更新资源获取次数
if (active != null) {
active.acquire();
}
return active;
}
内存缓存
如果ActiveResources中获取不到,就从内存中获取缓存,如果成功获取到缓存,就将缓存从内存中删除并将添加到ActiceResources中:1
2
3
4
5
6
7
8//如果ActiveResources中获取不到,就从内存中获取缓存
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
//获取到缓存资源,回调onResourceReady方法
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}
loadFromCache方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
//从内存中获取缓存,获取到缓存后,将缓存从内存中删除
EngineResource<?> cached = getEngineResourceFromCache(key);
//如果获取到缓存
if (cached != null) {
//更新资源的获取次数
cached.acquire();
//将获取到的资源添加到ActiceResources中
activeResources.activate(key, cached);
}
return cached;
}
磁盘缓存
如果在内存中获取不到缓存,就要执行以下步骤1
2
3
4
5
6
7EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
return new LoadStatus(cb, current);
}
其中jobs为Jobs对象,看Jobs的get方法:1
2
3EngineJob<?> get(Key key, boolean onlyRetrieveFromCache) {
return getJobMap(onlyRetrieveFromCache).get(key);
}
继续看getJobMap方法:1
2
3private Map<Key, EngineJob<?>> getJobMap(boolean onlyRetrieveFromCache) {
return onlyRetrieveFromCache ? onlyCacheJobs : jobs;
}
其中:onlyCacheJobs和jobs的定义如下:1
2private final Map<Key, EngineJob<?>> jobs = new HashMap<>();
private final Map<Key, EngineJob<?>> onlyCacheJobs = new HashMap<>();
最终,返回一个EngineJob对象,并且第一次加载的话返回null,继续执行下面语句1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
先创建EngineJob和DecodeJob,然后将EngineJob加到Jobs的Map中保存起来。EngineJob主要用于执行DecodeJob以及管理加载完成的回调,DecodeJob是一个Runnable,负责从磁盘或网络中加载数据。
EngineJob通过start方法执行DecodeJob1
2
3
4
5
6
7public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
可以看到,DecodeJob是在线程池里执行的,DecodeJob的run中又调用了runWrapped方法:
DecodeJob#runWrapped
1 | private void runWrapped() { |
其中状态的变化如下:
DecodeJob#getNextStage
1 | private Stage getNextStage(Stage current) { |
注意区分两种状态:在runWrapped方法中的状态为RunReason,在getNextStage方法中的状态为Stage。
在获取下一State时,调用DiskCacheStrategy对象的decodeCachedResource方法,用户可以通过设置相应的DiskCacheStrategy对象来配置Glide的硬盘缓存,用法如下:1
2
3
4
5Glide.with(MainActivity.this)
.applyDefaultRequestOptions(new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE))
.load(url)
.into(mPicIv);
调用diskCacheStrategy方法并传入DiskCacheStrategy.NONE后,就可以禁止掉硬盘缓存。硬盘缓存策略共有以下几种:
- DiskCacheStrategy.NONE:不缓存任何内容
- DiskCacheStrategy.DATA:只缓存原始图片
- DiskCacheStrategy.RESOURCE:只缓存转换后的图片
- DiskCacheStrategy.ALL:既缓存原始图片,也缓存转换后的图片
- DiskCacheStrategy.AUTOMATIC:让Glide根据图片资源智能选择策略(默认)
回到runWrapped方法,在确定了state后,根据state获得对应的DataFetcherGenerator,看getNextGenerator方法:
DecodeJob#getNextGenerator
1 | private DataFetcherGenerator getNextGenerator() { |
获得相应DataFetcherGenerator后,执行runGenerators方法:
DecodeJob#runGenerators
1 | private void runGenerators() { |
首先会执行对应Generator的startNext方法,假如state为RESOURCE_CACHE(磁盘缓存策略为DiskCacheStrategy.RESOURCE或DiskCacheStrategy.ALL),则执行ResourceCacheGenerator的startNext方法:
ResourceCacheGenerator#startNext
1 | public boolean startNext() { |
在该方法中,如果能够获取到缓存文件,就先得到能够加载这个缓存文件的ModelLoaders,之后通过ModelLoader构造出对应的LoadData,最后通过该LoadData的DataFetcher的loadData方法加载缓存文件的数据。
假如这里调用的ByteBufferFetcher的loadData方法:1
2
3
4
5
6
7
8
9
10
11
12
13
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super ByteBuffer> callback) {
ByteBuffer result;
try {
result = ByteBufferUtil.fromFile(file);
} catch (IOException e) {
callback.onLoadFailed(e);
return;
}
callback.onDataReady(result);
}
成功的话会回调onDataReady方法,将加载到的数据传出去,先存放在DecodeJob中,之后进行相关操作:1
2
3
4
5
6
7
8
9
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
//...
this.currentData = data;
//...
}
而失败的话会回调onLoadFailed方法,也是在DecodeJob中处理1
2
3
4
5
6
7
8
9
10
11
12
public void onDataFetcherFailed(Key attemptedKey, Exception e, DataFetcher<?> fetcher,
DataSource dataSource) {
//...
if (Thread.currentThread() != currentThread) {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
} else {
runGenerators();
}
}
可以看到,在加载数据失败后不会就此结束,而是重新执行runGenerators方法再次尝试获取数据。
对于第二种情况,假如state为DATA_CACHE(磁盘缓存策略为DiskCacheStrategy.DATA或DiskCacheStrategy.ALL),则执行DataCacheGenerator的startNext方法,该方法的过程ResourceCacheGenerator类似,就不多说了。
现在看第三种情况,当state为SOURCE时,对应的Generator为SourceGenerator,同样调用SourceGenerator的startNext方法:
SourceGenerator#startNext
1 |
|
该方法中,如果已经获取到了数据,就将数据添加到磁盘缓存中,之后如果可以从磁盘缓存加载数据就不再执行后面操作。否则的话,同样是先通过相应的ModelLoader获得LoadData对象,然后通过LoadData中的DataFetcher的loadData方法加载数据(例如通过url从网络加载图片资源)。
如果加载数据成功,会先在SourceGenerator中回调onDataReady方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
//判断是否要缓存到磁盘中
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
//如果要缓存到磁盘中,会先把数据保存起来,之后重新进入startNext方法(可能在其他子线程),将数据缓存到磁盘。
dataToCache = data;
cb.reschedule();
} else {
//不缓存到磁盘的话,就将数据传递出去,存放在DecodeJob中
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
加载成功时,会判断是否要缓存到磁盘(如果是从网络加载的资源,当磁盘缓存策略为DiskCacheStrategy.DATA和DiskCacheStrategy.ALL时,会缓存起来),要缓存到磁盘时,会重新进入startNext方法并将数据缓存到磁盘。如果不缓存到硬盘就将数据传递出去。
如果加载数据失败,同样会重新执行runGenerators方法再次尝试获取数据。
分析到这里,Engine的load方法算是分析完毕了,这个方法是真正用于加载数据的,通过该方法可以了解到Glide的缓存机制。这里小结一下Engine的load方法的执行步骤:
小结
Engine#load的执行步骤
- 生成缓存key:该key是一个EngineKey对象,由传入的多个参数生成。之后通过缓存key获取缓存资源
- 首先从ActiveResources中获取缓存,ActiveResources是第一级缓存,它通过HashMap来存储数据,数据保存在WeakReference中,此外还有一个引用队列,当某个数据的弱引用对象被gc掉之后,其对应的Reference对象会加入到引用队列中。从引用队列获取到被gc掉的弱引用对象后就可以将这些对象从ActiveResources中删除。
- 如果ActiveResources中获取不到,就从内存中获取,内存缓存是第二级缓存。内存缓存使用了LRU算法,内部使用LinkedHashMap存储数据。LinkedHashMap内部可以设置为按照访问顺序进行排序,最近最少访问的在前面,很适合LRU算法。如果成功获取到缓存,就将缓存从内存中删除并将添加到ActiceResources中。
- 如果内存中也获取不到缓存,就会创建EngineJob和DecodeJob,DecodeJob是一个Runnable。EngineJob将DecodeJob提交到线程池中执行。DecodeJob根据资源的磁盘缓存策略,是缓存原图还是缓存转换后的图片,从磁盘中获取不同的缓存文件,获取缓存文件成功后,先得到能够加载该缓存文件的ModelLoader,之后通过ModelLoader构造出对应的LoadData,最后通过该LoadData的DataFetcher加载缓存文件的数据。加载数据成功会将数据传递出去,失败的话会重新尝试获取缓存文件并加载数据。
- 如果不能从磁盘中得到缓存,会继续在DecodeJob中通过相应的ModelLoader获得LoadData对象并加载数据,不过这次是从数据源加载数据,例如从通过url从网络加载数据。加载数据成功后,会判断是否要缓存到磁盘要缓存到磁盘时,会重新进入startNext方法并将数据缓存到磁盘。如果不缓存到硬盘就将数据传递出去。加载数据失败的话会重新尝试加载,因为可能其他的ModelLoader可以加载成功,所以要不断尝试,直到所有ModelLoader都尝试完。