文章目录
一、概述
1.1 背景
Glide 是一个图片加载库,跟它同类型的库还有 Picasso、Fresco、Universal-Image-Loader 等。基于 Glide 优秀的缓存管理策略和生命周期关联的特点,目前市面上对 Glide 的使用非常广,因此我们有必要深入研究下 Glide 相关的实现原理,便于更好的使用它。
在前面三篇文章中,我们介绍了 Glide 框架的整体结构,以及 Glide 中关于 DataLoaderProvider
、ModelLoader
和 ResourceTranscoder
注册部分的逻辑。本文我们主要来分析一下 Glide 中5层缓存的设计。
Glide 版本:
Glide 3.7.0
Github 地址:https://github.com/bumptech/glide/tree/v3.7.0
Gradle 依赖:implementation 'com.github.bumptech.glide:glide:3.7.0'
1.2 系列文章
- Glide系列(一) — Glide 框架结构浅析
- Glide系列(二) — DataLoadProvider 及与 Encoder&Decoder 的关系
- Glide系列(三) — LoadProvider、ModelLoader、DataFetcher 和 ResourceTranscoder 关系
- Glide系列(四) — Glide 缓存流程分析
- DiskLruCache 源码分析
二、准备知识
在分析 Glide 缓存结构之前,我们先回顾下 Glide系列(一) — Glide 框架结构浅析 中提到的 Glide 的整体分层结构。
2.1 Glide 的缓存分层结构
小结:
- Glide 的整体结构与网络请求类似,都分为请求和响应两部分。
- Glide 框架主要分为两大流程:
- 图片请求的构建流程。
- 图片缓存的获取流程。
- 图片缓存一共分为3大层,5小层:
- 内存缓存(2小层):弱引用缓存、LruCache。
- 本地缓存(2小层):本地 ResultCache 缓存、本地 SourceCache 缓存,通过 DiskLruCache 进行文件管理。
- Source 数据源(1小层):网络获取、本地 AssetPath 获取、其它本地图片。
2.2 Glide 缓存相关类的关联关系
下图展示了缓存相关类的关联关系,以及个数的对应关系。
小结:
- Engine: 是个单例,负责具体执行内存缓存获取逻辑,并触发磁盘缓存的获取流程 (磁盘缓存的获取流程其实是由内部的 EngineJob 和 EngineRunnable 代理)。
- ActiveResources: 第一层缓存(内存缓存),主要缓存的是屏幕正在使用中的图片,它使用的是弱引用进行缓存,它与Engine是一对一的关系。这里就有一个面试题,为什么要使用弱引用缓存,强引用可以吗,为什么?
- LruResourceCache: 第二层缓存(内存缓存),主要是缓存ActiveResources中引用计数为0的数据,它与Engine是一对一的关系。
- EngineJob: 每个图片请求都对应一个 EngineJob,它主要负责从磁盘获取缓存后将数据回传给Request的逻辑。它对Engine是多对一的关系。
- EngineRunnable: 是一个获取缓存的任务,它与 EngineJob 是一对一的关系。
- DecodeJob: 主要功能是负责从第三层到第5层缓存的获取并进行解码,在 EngineRunnable 中会执行 DecodeJob 对应的方法进行缓存的获取。内部含5部分能力。
- 有提供编解码能力的 DataLoadProvider。
- 提供数据源获取能力的 DataFetcher (第五层缓存)。
- 提供数据处理的 Transformation。
- 提供数据类型变换的 ResourceTranscoder。
- 提供获取缓存文件能力的 DiskCacheProvider。
- DiskCache:负责管理缓存文件的存/取 (第三、四层缓存)。
三、缓存的获取流程
上面我们介绍了缓存的层级,以及与缓存相关的几个类之间的关联关系,下面我们从源码的角度来分析一下这一过程。
主要从下面几个角度分析:
- 缓存获取的入口。
- 内存缓存的获取。
- 磁盘缓存的获取。
3.1 缓存获取的入口
前面我们提到,Glide 获取图片的操作类似网络请求,下面我们就先来分析下缓存获取的入口。
// GenericRequest.class
public void onSizeReady(int width, int height) {
status = Status.RUNNING;
width = Math.round(sizeMultiplier * width);
height = Math.round(sizeMultiplier * height);
// 获取ModelLoader
ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
// 获取 DataFetcher
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
// 获取 ResourceTranscoder
ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
loadedFromMemoryCache = true;
// 调用 Engine.load() 方法执行缓存加载逻辑。
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);
}
小结:
- 在调用
Engine.load()
方法之前,就从注册表里找到了与加载数据相关的ModelLoader、DataFetcher、ResourceTranscoder
。- 此处的 loadProvider 是 FixedLoadProvider 类型,为什么不直接传入 load 方法内?
- load() 方法接收 DataLoadProvider 类型参数,遵循类的单一职责。
- load() 方法传入的最后一个参数类型为 ResourceCallback,它是 Request 与 Engine 关联的纽带。
3.2 内存缓存的查询
前面我们已经提到了获取缓存的入口,下面我们分析一下
Engine.load()
方法具体做了些什么。
// Engine.class
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
// 1.生成EngineKey,它用于第1,2,3层缓存的查找。里面包含了所需图片的尺寸、解码器等信息。
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
// 2.先从第2层缓存中查找。
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached);
return null;
}
// 2.再从第1层缓存中查找。
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active);
return null;
}
// jobs 是一个Map集合,维护了当前正在执行数据请求的任务。当需要取消所有任务时,方便操作。
// 3.如果同一个图片被请求两次,会将第二次的请求直接关联到第1次的请求上,避免重复请求。
EngineJob current = jobs.get(key);
if (current != null) {
current.addCallback(cb);
return new LoadStatus(cb, current);
}
// 4.执行磁盘缓存查询逻辑。
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
// 这里启动EngineRunnable任务。
engineJob.start(runnable);
return new LoadStatus(cb, engineJob);
}
// 第二层内存缓存查找逻辑
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
//...
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
// 缓存到第一层activeResources内存缓存中。
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
// 从第二层内存缓存中移除
Resource<?> cached = cache.remove(key);
final EngineResource result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource) cached;
} else {
result = new EngineResource(cached, true);
}
return result;
}
// 第一层内存缓存查找逻辑
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
//...
EngineResource<?> active = null;
WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
if (activeRef != null) {
active = activeRef.get();
if (active != null) {
active.acquire(); //引用计数
} else {
activeResources.remove(key);
}
}
return active;
}
小结:
Engine.load()
方法主要处理了2层内存缓存的查询逻辑。先从第二层内存缓存中查询,再从第一层内存缓存中查询。- 如果从第二层内存缓存中找到缓存后,要将缓存从第二层缓存移除,并加入到第一层缓存中。
- 第一层缓存使用弱应用进行缓存,便于当弱引用中的数据没有被使用,且仍缓存在第一层缓存中时被回收。如果使用强引用代替,则当该缓存资源没有被使用时,占用的内存资源也不能被释放。
- Engine 中通过一个 Map 集合缓存当前正在执行的 EngineJob。当需要取消所有任务时,方便操作。
- 缓存 EngineJob 的另一个好处是:如果同一个图片被请求两次,会将第二次的请求直接关联到第1次的EngineJob 上,避免重复请求。
- 执行后面三层磁盘缓存查询逻辑。
3.2 磁盘缓存的查询
上面介绍了磁盘缓存的查询流程具体交给 EngineJob 和 EngineRunnable来执行,下面我们就来分析一下。
// Engine.load 方法
// EngineJob 主要是负责图片缓存获取到之后的逻辑处理,如回传给Request。
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
// EngineRunnable是具体执行加载逻辑的类,所以要将EngineJob传入,方便将获取到的缓存数据回传给EngineJob,继而传递到Request中。
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
// 里面是一个线程池,用于执行EngineRunnable任务。
engineJob.start(runnable);
小结:
- EngineJob 内部持有两个线程池。
- DiskCacheService 线程池:用于 ResultCache、SourceCache 的缓存加载解析。
- SourceSrevice 线程池:用于 Source 数据源的加载解析。
- EngineRunnable 内传入 EngineJob:EngineJob 主要负责缓存获取后的逻辑处理,并与 Request 通信。EngineRunnable 是具体执行缓存加载逻辑的类,所以要将 EngineJob 传入,是方便获取到缓存时通过 EngineJob 传递到 Request 中。
下面我们重点看下
EngineRunnable.run()
方法。
// EngineRunnable.class
public void run() {
//...
Resource<?> resource = null;
try {
// 1.获取缓存数据并使用Decoder进行解析。
resource = decode();
} catch (Exception e) {
}
//...
if (resource == null) {
// 数据获取失败
onLoadFailed(exception);
} else {
// 数据获取成功
onLoadComplete(resource);
}
}
// stage 默认是Stage.CACHE。
this.stage = Stage.CACHE;
private boolean isDecodingFromCache() {
return stage == Stage.CACHE;
}
private Resource<?> decode() throws Exception {
// 是否是从磁盘缓存中解析,默认是true。
if (isDecodingFromCache()) {
return decodeFromCache();
} else {
return decodeFromSource();
}
}
private void onLoadFailed(Exception e) {
if (isDecodingFromCache()) {
// 如果当stage为Stage.CACHE时,走到onLoadFailed逻辑,说明磁盘缓存里没有数据,因此将stage设置为Stage.SOURCE,并重新执行EngineRunnable.run()方法。
stage = Stage.SOURCE;
// SourceService 线程池
manager.submitForSource(this);
} else {
manager.onException(e);
}
}
private void onLoadComplete(Resource resource) {
manager.onResourceReady(resource);
}
// ---------------- Another Class --------------------
// EnginJob.class
public void onResourceReady(final Resource<?> resource) {
this.resource = resource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
private void handleResultOnMainThread() {
//...
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
engineResource.acquire();
// 1.保存到第一层内存缓存
listener.onEngineJobComplete(key, engineResource);
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
// 2.回传给Request的Callback
cb.onResourceReady(engineResource);
}
}
engineResource.release();
}
小结:
EngineRunnable.run()
方法的流程比较简单,主要分为2个步骤:
- 查询缓存并进行 decode 解析。
- 判断返回值是否存在,不存在就执行 onLoadFailed 逻辑,成功就执行 onLoadComplete 逻辑。
isDecodingFromCache()
方法用于判断当前解析的数据是来自缓存还是来自第五次缓存(数据源)。onLoadFailed()
方法:当stage为Stage.CACHE时,触发了onLoadFailed逻辑,说明磁盘缓存里没有数据,因此将stage设置为Stage.SOURCE,并重新执行EngineRunnable.run()方法,因此会从第五次缓存(数据源)查找数据。onLoadComplete()
方法:decode() 方法获取到数据后,会执行 onLoadComplete 方法。该方法内的 manager 为 EnginJob 对象,所以会调用EnginJob.onResourceReady()
方法将数据回传到 Request。- EngineRunnable 里的 stage 有点像状态机,通过为stage设置不同状态并重新触发EngineRunnable任务来实现。
接下来我们重点看下
EngineRunnable.decodeFromCache()、EngineRunnable.decodeFromSource
两个方法。
Condition1:ResultCache 缓存
EngineRunnable.decodeFromCache()
// EngineRunnable.class
private Resource<?> decodeFromCache() throws Exception {
Resource<?> result = null;
try {
//1.先从ResultCache获取缓存。
result = decodeJob.decodeResultFromCache();
} catch (Exception e) {
}
if (result == null) {
//2.再从SourceCache获取缓存。
result = decodeJob.decodeSourceFromCache();
}
return result;
}
小结:
- 磁盘缓存分为
ResultCache、SourceCache
两部分,所以这里的逻辑也分两步来执行。
- ResultCache:经过 transform 处理之后的数据。
- SourceCache:从 Source 加载的数据,未经过任何处理。
EngineRunnable.decodeFromCache()
方法会先从 ResultCache 中获取,如果没有缓存,再从SourceCache中获取缓存。
DecodeJob.decodeResultFromCache()
public Resource<Z> decodeResultFromCache() throws Exception {
if (!diskCacheStrategy.cacheResult()) {
return null;
}
// 1.从DiskLruCache中获取ResultCache缓存,这里的key为EngineKey。
Resource<T> transformed = loadFromCache(resultKey);
// 2.将Resource<T>类型的数据transformed转码变成另一个类型(Resource<Z>)的数据result。
// transcode()方法内调用transcoder.transcode(transformed)。
Resource<Z> result = transcode(transformed);
return result;
}
private Resource<T> loadFromCache(Key key) throws IOException {
// 1.1 从DiskLruCache中获取缓存。
File cacheFile = diskCacheProvider.getDiskCache().get(key);
if (cacheFile == null) {
return null;
}
Resource<T> result = null;
try {
// 1.2 通过解码器对缓存文件进行解码。
result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
} finally {
if (result == null) {
diskCacheProvider.getDiskCache().delete(key);
}
}
return result;
}
小结:
loadFromCache(resultKey)
方法:从DiskLruCache中获取 ResultCache 缓存,这里的 key 为 EngineKey。- 1.1 通过 DiskLruCache 获取 ResultCache 缓存文件。
- 1.2 通过解码器对缓存文件进行解码。
transcode()
方法:将Resource类型的数据转码变成另一个类型(Resource)的数据。
Condition2:SourceCache 缓存
DecodeJob.decodeSourceFromCache()
// DecodeJob.class
public Resource<Z> decodeSourceFromCache() throws Exception {
if (!diskCacheStrategy.cacheSource()) {
return null;
}
// 1.从DiskLruCache中获取SourceCache缓存,这里的key为OriginalKey,
// 这部分前面已经分析过了。
Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
// 2.执行transform、Encoder、Transcode操作。
return transformEncodeAndTranscode(decoded);
}
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
// 2.1 执行transform操作。我们外部传入的transform就是在这里执行的。
Resource<T> transformed = transform(decoded);
// 2.2 将内存数据写入的磁盘中,生成ResultCache缓存。
writeTransformedToCache(transformed);
// 2.3 将Resource<T>类型的数据转码变成另一个类型(Resource<Z>)的数据,这部分前面已经分析过了。
Resource<Z> result = transcode(transformed);
return result;
}
private void writeTransformedToCache(Resource<T> transformed) {
//...
// 将内存数据写入的磁盘中,生成ResultCache缓存。
SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
diskCacheProvider.getDiskCache().put(resultKey, writer);
}
小结:
- 从 DiskLruCache 中获取 SourceCache 缓存,这里的 key 为 OriginalKey。
- 1.1 通过 DiskLruCache 获取 SourceCache 缓存文件。
- 1.2 通过解码器对缓存文件进行解码。
- 执行transform、Encoder、Transcode操作。
- 2.1 执行transform操作。我们外部传入的transform就是在这里执行的。
- 2.2 将内存数据写入的磁盘中,生成ResultCache缓存。
- 2.3 将Resource类型的数据transformed转码变成另一个类型(Resource)的数据result。
- 关于 DiskLruCache 的内容可以参考:DiskLruCache 源码分析
Condition3:Source 缓存
下面我们分析最后一种常见,直接从数据源加载数据。
EngineRunnable.decodeFromSource
// EngineRunnable.class
private Resource<?> decodeFromSource() throws Exception {
// 从Source数据源获取缓存
return decodeJob.decodeFromSource();
}
// ---------------- Another Class --------------------
// DecodeJob.class
public Resource<Z> decodeFromSource() throws Exception {
// 1.加载并解析数据源(加载数据+对数据解码+SourceCache缓存)。
Resource<T> decoded = decodeSource();
// 2.执行transform、Encoder、Transcode操作。
return transformEncodeAndTranscode(decoded);
}
private Resource<T> decodeSource() throws Exception {
Resource<T> decoded = null;
try {
// 1.1 使用DataFetcher加载数据源数据。
final A data = fetcher.loadData(priority);
if (isCancelled) {
return null;
}
// 1.2 将数据到SourceCache缓存层,并重新触发第4层缓存的查找逻辑。
decoded = decodeFromSourceData(data);
} finally {
fetcher.cleanup();
}
return decoded;
}
private Resource<T> decodeFromSourceData(A data) throws IOException {
final Resource<T> decoded;
if (diskCacheStrategy.cacheSource()) {
// 使用 DiskLruCache 进行数据缓存,并重新触发从第4层缓存查找逻辑。
decoded = cacheAndDecodeSourceData(data);
} else {
decoded = loadProvider.getSourceDecoder().decode(data, width, height);
}
return decoded;
}
private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
// 1.2.1 使用 DiskLruCache 进行数据缓存。
SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
// 1.2.2 执行第4层缓存的查找逻辑(前面已经介绍过)。
Resource<T> result = loadFromCache(resultKey.getOriginalKey());
return result;
}
小结:
- 加载并解析数据源(加载数据+对数据解码+SourceCache缓存)。
- 1.1 使用DataFetcher加载数据源数据。
- 1.2 将数据到SourceCache缓存层,并重新触发第4层缓存的查找逻辑。
- 1.2.1 使用 DiskLruCache 进行数据缓存 (缓存到4级磁盘缓存)。
- 1.2.2 执行第4层缓存的查找逻辑:根据OriginalKey从 DiskLruCache 中查找缓存文件,并进行解码。
- 执行transform、Encoder、Transcode操作。
- 2.1 执行transform操作。我们外部传入的transform就是在这里执行的。
- 2.2 将内存数据写入的磁盘中,生成ResultCache缓存 (缓存到3级磁盘缓存)。
- 2.3 将Resource类型的数据transformed转码变成另一个类型(Resource)的数据result。
3.3 缓存查询流程图
到这里,5种缓存的获取流程就分析结束了,下面我们看下这5种获取缓存场景的流程图。
小结:
- 蓝色部分为缓存查询流程。
- 黄色部分为缓存的保存流程。
- 从第五层缓存数据写入第四层缓存后,会重新触发第4层缓存的查询逻辑。
四、总结
- 了解 Glide 的缓存分层结构,及与 Request 是如何关联的。
- 了解缓存的查询流程,特别是要注意优先查询第二层内存缓存。
- 了解缓存的保存逻辑。
- 第二层内存缓存 -> 第一次内存缓存
- 第五层数据源缓存 -> 第四层磁盘缓存 -> 第一次内存缓存
- 第四层磁盘缓存 -> 第三层磁盘缓存 -> 第一次内存缓存
- 了解缓存查询相关类的关联关系,及其作用。
- 了解缓存的数据变换操作 (ResourceTranscoder)。
五、碎碎念
到这里,Glide 的核心功能就已经分析完了,剩余的话题有空再写吧。诸如:
- 生命周期关联。
- Module 的自定义。
- 缩略图与原图加载的协同原理。