一开始是想记录下如何设置磁盘的缓存大小和位置,结果看到一篇文章介绍的很好,就自己跟着源码看了一遍也记录一下吧。
一、Glide加载资源流程
-
Glide使用了ActiveResources(活动缓存弱引用)+MemoryCache(内存缓存Lru算法)+DiskCache(磁盘缓存Lru算法)。
-
ActiveResources:存储当前界面使用到的图片。界面不展示后,该Bitmap又被缓存至MemoryCache中,并从ActiveResources中删除。
-
Memory Cache:存储当前没有使用到的Bitmap,当MemoryCache中得到Bitmap后,该Bitmap又被缓存至ActiveResources中,并从MemoryCache中删除。
-
Disk Cache:持久缓存。例如图片加圆角,处理后图片会被缓存到文件中,应用被再次打开时可以加载缓存直接使用。
注意: ActiveResources + MemoryCache是内存缓存,都属于运行时缓存,且互斥(同一张图片不会同时缓存在ActiveResources+MemoryCache),应用被杀死后将不存在。
二、Glide 的缓存分层结构
三、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:负责管理缓存文件的存/取 (第三、四层缓存)。
Lru
内存缓存:使用LinkedHashMap
来缓存资源(强引用),并设定一个缓存的大小。如果有资源被访问到,首先会在链表中删除该节点,然后再添加到链表头,这样就可以保证链表头部的节点是最近访问过的。而当缓存的数量达最大值的时候,就会将链表尾部(最近最少使用)的数据移除。
四、Glide的缓存Key
介绍缓存流程之前先介绍下EngineKey
4.1 作用
缓存key是实现内存和磁盘缓存的唯一标识
4.2 原理
重写equals和hashCode方法,来确保只有key对象的唯一性
4.3 生成Key
生成缓存Key的代码在Engine类的load()方法当中:
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height,
loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(),
transformation, loadProvider.getEncoder(),transcoder,
loadProvider.getSourceEncoder());
每一次的load方法内部,都会有一个fetcher.getId()方法获得了一个id字符串,这个字符串也就是我们要加载的图片的唯一标识,比如说如果是一张网络上的图片的话,那么这个id就是这张图片的url地址。然后,这个id会结合signature、width、height等等10个参数一起传入到EngineKeyFactory的buildKey()方法当中,从而构建出了一个EngineKey对象,这个EngineKey也就是Glide中的缓存Key了。 因此,如果你图片的width或者height发生改变,也会生成一个完全不同的缓存Key。
五、缓存的获取流程
上面我们介绍了缓存的层级、与缓存相关的几个类之间的关联关系,以及缓存Key,下面我们从源码的角度来分析一下这一过程。
主要从下面几个角度分析:
- 缓存获取的入口。
- 内存缓存的获取。
- 磁盘缓存的获取。
5.1 缓存获取的入口
Glide 获取图片的操作类似网络请求,下面我们就先来分析下缓存获取的入口
public <Y extends Target<TranscodeType>> Y into(Y target) {
...
Request request = buildRequest(target);
target.setRequest(request);
lifecycle.addListener(target);
requestTracker.runRequest(request);
return target;
}
由.into(iv)开始会创建一个Request,继续跟进源码发现创建的是GenericRequest
// 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 关联的纽带。
5.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 上,避免重复请求。
- 执行后面三层磁盘缓存查询逻辑。
5.3 磁盘缓存的查询
上面介绍了磁盘缓存的查询流程具体交给 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
两个方法。
// 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.class
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。
- 通过 DiskLruCache 获取 ResultCache 缓存文件。
- 通过解码器对缓存文件进行解码。
- transcode() 方法:将Resource类型的数据转码变成另一个类型(Resource)的数据。
// 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);
}
1.从 DiskLruCache 中获取 SourceCache 缓存,这里的 key 为 OriginalKey。
1.1 通过 DiskLruCache 获取 SourceCache 缓存文件。
1.2 通过解码器对缓存文件进行解码。
2.执行transform、Encoder、Transcode操作。
2.1 执行transform操作。我们外部传入的transform就是在这里执行的。
2.2 将内存数据写入的磁盘中,生成ResultCache缓存。
2.3 将Resource类型的数据transformed转码变成另一个类型(Resource)的数据result。
关于 DiskLruCache 的内容可以参考:DiskLruCache 源码分析
下面我们分析最后一种常见,直接从数据源加载数据。
// 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;
}
1.加载并解析数据源(加载数据+对数据解码+SourceCache缓存)。
1.1 使用DataFetcher加载数据源数据。
1.2 将数据到SourceCache缓存层,并重新触发第4层缓存的查找逻辑。
1.2.1 使用 DiskLruCache 进行数据缓存 (缓存到4级磁盘缓存)。
1.2.2 执行第4层缓存的查找逻辑:根据OriginalKey从 DiskLruCache 中查找缓存文件,并进行解码。
2.执行transform、Encoder、Transcode操作。
2.1 执行transform操作。我们外部传入的transform就是在这里执行的。
2.2 将内存数据写入的磁盘中,生成ResultCache缓存 (缓存到3级磁盘缓存)。
2.3 将Resource类型的数据transformed转码变成另一个类型(Resource)的数据result。
5.4 缓存查询流程图
六、关于磁盘缓存如何配置磁盘缓存大小和位置
需要实现GlideModule或AppGlideModule并在AndroidManifest.xml配置
public class MyAppGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// 配置磁盘缓存大小和位置
int diskCacheSize = 1024 * 1024 * 500;// 500M
String diskCacheDir = context.getCacheDir() + "/image_cache";
// 创建磁盘缓存
DiskLruCacheFactory diskCacheFactory = new DiskLruCacheFactory(diskCacheDir, diskCacheSize);
builder.setDiskCache(diskCacheFactory);
}
@Override
public void registerComponents(Context context, Glide glide) {
//配置glide使用okhttp
glide.register(GlideUrl.class, InputStream.class,new OkHttpUrlLoader.Factory());
}
}
<application>
...
<meta-data
android:name="xxx.xxx.MyAppGlideModule"
android:value="GlideModule" />
...
</application>
七、总结
- 了解 Glide 的缓存分层结构,及与 Request 是如何关联的。
- 了解缓存的查询流程,特别是要注意优先查询第二层内存缓存。
- 了解缓存的保存逻辑。
- 第二层内存缓存 -> 第一次内存缓存
- 第五层数据源缓存 -> 第四层磁盘缓存 -> 第一次内存缓存
- 第四层磁盘缓存 -> 第三层磁盘缓存 -> 第一次内存缓存
- 了解缓存查询相关类的关联关系,及其作用。
- 了解缓存的数据变换操作 (ResourceTranscoder)。
原文链接:https://blog.csdn.net/Love667767/article/details/127601652