这是关于Glide框架系统分析的第三篇文章,这里涉及到Glide最最常考的知识原理:缓存机制。这里的考题往往会围绕以下两点来问:
-
Glide到底有几级缓存?这些缓存都是什么?
-
当Glide获取资源后,处理资源的流程是什么样的?
不得不说,这整个流程中发生的事情实在是太多了,看的我有些应接不暇。所以这里我们只围绕着主题来说,目的是能够准确回答上面提出的两个问题即可。下面我们就围绕下图的时序,来分析多级缓存的主要过程。在图中我用三种颜色标记了三个流程步骤,分别是 内存缓存(红色)->磁盘缓存(蓝色)->请求源(绿色)。咱们就逐一探索每一块的流程吧。
内存缓存
内存的缓存主要分为两类,第一类就是通过loadFromActiveResources(key, isMemoryCacheable);
方法,于ActiveResources
容器结构中获取活跃中的资源。内存缓存的命中流程看起来相对比较简单,在时序图中均以红色呈现。
ActiveResources
该流程过程用序号1
标识。
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {//来自于options中的配置项,是否允许从缓存获取资源。适用于某些特殊场景。
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();//资源引用自增1
}
return active;
}
若未命中,则通过loadFromCache()
方法,从MemoryCache
容器中获取缓存的资源。这个MemoryCache如果没有被指定的话,默认是LruResourceCache
。它的赋值时机是在Glide初始化时,也就是在调用Glide.with()过程中的前半截:执行GlideBuilder.build的过程中赋值的。从MemoryCache中获取到包装为EngineResource
的资源,不过这里用的是remove操作。同时,将该资源的引用计数加1,并将该资源添加到ActiveResources容器中。
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();//命中缓存资源引用自增1
activeResources.activate(key, cached);//加入到ActiveResources容器。
}
return cached;
}
LruResourceCache
Glide的MemoryCache协议下一共有两个子类,LruResourceCache
和MemoryCacheAdapter
。
在内存缓存的第二阶段,默认使用的是LruResourceCache,它继承了LruCache作为父类。而实现Lru算法的容器很简单,就是LinkedHashMap。
LinkedHashMap有一个构造函数:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
这第三个参数的意思是,按照加入的顺序作为LinkedHashMap的顺序,因此其本身就已经支持Lru的算法核心要素了。那就是常用的value一定会被频繁的remove和put到队尾,那么一旦达到了Lru的maxSize上线,只需要从队头开始删除即可。源码中也是这么做的。 再往后看,于Engine.load()中,发现命中的Resource资源,被加入到了第一步的缓存:ActiveResources中去,引用计数自增1。
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {//命中二级内存缓存
cached.acquire();//资源引用自增1
activeResources.activate(key, cached);//加入到ActiveResources容器,作为一级缓存。
}
return cached;
}
在第二阶段——LruResourceCache中命中的资源,会首先被从中remove掉。然后资源引用计数值自增,并将资源放入一级内存中。
文件缓存
经过两级内存缓存查找均未命中时,Glide会先判断是否已经有执行中的EngineJob
。若有,则返回状态,等待Job回调。否则,实例化新的EngineJob
对象,启动执行分级查找方案。EngineJob
和DeocdeJob
两个对象相互依赖。他们之间的关系可以用下图来表示:
文件缓存概述——父类:DataFetchGenerator
对于文件级缓存来说,Glide中提供了三种对象,分别是ResourceCacheGenerator
,DataCacheGenerator
,SourceGenerator
。他们都继承了DataFetchGenerator
。
简单的说,DataFetchGenerator就是在对应的缓存级别上,做以下三件事儿:
startNext()
, 寻找能够处理当前Model的ModelLoader对象,并返回是否已启动ModelLoader执行FetchData的操作- 若未找到能够执行的ModelLoader,则
return false
。DecodeJob会执行下一层级的缓存任务。 - 若找到了ModelLoader和对应的DataFetcher,则执行loadData(),并回调给DataFetchGenerator。DataFetcherGenerator会回调给DecodeJob,根据数据获取成功与否进行不同的流程。
一级文件缓存——ResourceCacheGenerator
这是基于磁盘缓存的第一级存储,它的startNext()方法如下:
@Override
public boolean startNext() {
List<Key> sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
// TODO(b/73882030): This case gets triggered when it shouldn't. With this assertion it causes
// all loads to fail. Without this assertion it causes loads to miss the disk cache
// unnecessarily
// throw new IllegalStateException(
// "Failed to find any load path from " + helper.getModelClass() + " to "
// + helper.getTranscodeClass());
}
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
if (resourceClassIndex >= resourceClasses.size()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
resourceClassIndex = 0;
}
Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = helper.getTransformation(resourceClass);
// PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,
// we only run until the first one succeeds, the loop runs for only a limited
// number of iterations on the order of 10-20 in the worst case.
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
ResourceCacheGenerator对于modelLoader的搜索流程存在一个双循环,首先找到source列表,然后根据每一个source寻找对应的resource列表。因此这里区分了两个概念:source 和 resource,一个是未经适配的原始资源缓存,另一个是经过宽高等其他配置设置生效后的资源缓存。
startNext()开始首先获取基于model的原始资源Key列表sourceIds。可以说sourceIds中的每一个元素都指向了之前缓存下来的原始数据,并没有经过宽高、大小等缩放处理的数据。然后,遍历sourceIds,通过sourceId,width、height、和options等配置内容生成具备唯一性的ResourceCacheKey
,通过modelLoaders找到LoadData,再通过其DataFetcher执行loadData操作。由于loadData不是本文的重点,咱就不展开探索了。
这样下来,ResourceCacheGenerator的主流程就结束了,如果未命中,则经DecodeJob的调度,启动下个级别的Generator:DataCacheGenerator
。
二级文件缓存——DataCacheGenerator
这是基于磁盘存储的第二级存储方式,其startNext()方法如下:
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
// PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times
// and the actions it performs are much more expensive than a single allocation.
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData =
modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
第二级的缓存执行startNext()方法相对来说比较简单,它的目的是找到原始资源,因为在经过第一级缓存搜寻过后没有找到满足当前配置的缓存资源,因此会开始寻找未经修改适配的原始缓存资源。这里只有一个维度的搜索。其方法和原理与ResourceCacheGenerator类似。 如若该层级的缓存依然没有命中,则说明当前需要加载的数据在整个缓存系统中是不存在的,那就需要Glide去亲自从图片原图去请求数据。根据DataCacheGenerator的返回值,经过DecodeJob重新调度,启动SourceGenerator。
源头获取——SourceGenerator
当上述4步缓存策略均未命中时,最终会执行SourceGenerator。与之前的两类DataFetcherGenerator所不一样的是,这里的startNext()不再从cacheKeys缓存中获取数据,也不用再次生成各种Key对象,而是直接从DecodeHelper中获取loadData列表,匹配到DataFetcher执行。
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);//直接获取LoadData列表,不再纠结Keys和缓存相关逻辑。
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);//找到能够处理的DataFetcher,执行loadData操作。
}
}
return started;
}
private boolean hasNextModelLoader() {
return loadDataListIndex < helper.getLoadData().size();
}
最终我们回顾一下在缓存查找的过程中都发生了什么:
- 去活跃缓存查找。
- 去内存缓存查找。
- 去文件缓存系统查找经过修饰加工的资源。
- 去文件缓存系统查找未经修饰加工的原始资源。
- 去源头下载,获取新鲜的原始资源。
新获取到的资源如何处理
当新鲜的资源获取到时,
- 获取到数据的DataFetcher回调至SourceGenerator中的onDataReady(),告知数据获取成功。
- onDataReady()判断DiskCacheStrategy.isDataCacheable。如果是true,通过回调DecodeJob.reschedule()重走一遍流程,使得原图所对应的originKey等属性内容能够整体存入DataCacheGenerator级别的缓存中,并通过其缓存命中的DataFetcher的
onDataReady()
回调给DecodeJob。 - 如果2中isDataCacheable是false,直接走onDataFetcherReady()回调给DecodeJob。
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
//若是isDataCacheable,则通过reschedule去切换到Glide所在线程中,执行缓存任务。
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
cb.reschedule();
} else {//若不允许DataCache缓存,则直接将原始数据通知回去。
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
- DecodeJob执行decodeFromRetrievedData()对原始Data进行处理获取Resource。并根据DiskCacheStrategy.isResourceCacheable判断是否存储Resource进缓存。
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey;
this.currentData = data;
this.currentFetcher = fetcher;
this.currentDataSource = dataSource;
this.currentAttemptingKey = attemptedKey;
if (Thread.currentThread() != currentThread) {//线程判断,若不是Glide所在的当前线程,则通过EngineJob的调度,回到Glide线程,执行Decode操作。
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
try {
decodeFromRetrievedData();//若已经是Glide线程,则执行decode操作。
} finally {
GlideTrace.endSection();
}
}
}
- 在DecodeJob中调用notifyComplete(),通过
onResourceReady()
回调给EngineJob,通知EngineJob在主线程处理Resource。
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
if (resource instanceof Initializable) {
((Initializable) resource).initialize();
}
Resource<R> result = resource;
LockedResource<R> lockedResource = null;
if (deferredEncodeManager.hasResourceToEncode()) {
lockedResource = LockedResource.obtain(resource);
result = lockedResource;
}
notifyComplete(result, dataSource);//先发出通知
stage = Stage.ENCODE;
try {
if (deferredEncodeManager.hasResourceToEncode()) {
deferredEncodeManager.encode(diskCacheProvider, options);//再进行一级文件缓存
}
} finally {
if (lockedResource != null) {
lockedResource.unlock();
}
}
// Call onEncodeComplete outside the finally block so that it's not called if the encode process
// throws.
onEncodeComplete();
}
private void notifyComplete(Resource<R> resource, DataSource dataSource) {
setNotifiedOrThrow();
callback.onResourceReady(resource, dataSource);
}
- EngineJob中收到回调,执行
handleResultOnMainThread()
。handlResultOnMainThread()
中组装EngineResource,并加入到ActiveResource缓存中,resource引用计数自增。
public void onResourceReady(Resource<R> resource, DataSource dataSource) {//收到回调
this.resource = resource;
this.dataSource = dataSource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();//主线程传递消息
}
public boolean handleMessage(Message message) {//主线程运行,处理携带资源的EngineJob。
EngineJob<?> job = (EngineJob<?>) message.obj;
switch (message.what) {
case MSG_COMPLETE:
job.handleResultOnMainThread();
break;
case MSG_EXCEPTION:
job.handleExceptionOnMainThread();
break;
case MSG_CANCELLED:
job.handleCancelledOnMainThread();
break;
default:
throw new IllegalStateException("Unrecognized message: " + message.what);
}
return true;
}
void handleResultOnMainThread() {
stateVerifier.throwIfRecycled();
if (isCancelled) {
resource.recycle();
release(false /*isRemovedFromQueue*/);
return;
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
} else if (hasResource) {
throw new IllegalStateException("Already have resource");
}
engineResource = engineResourceFactory.build(resource, isCacheable);//构建engineResource
hasResource = true;
// Hold on to resource for duration of request so we don't recycle it in the middle of
// notifying if it synchronously released by one of the callbacks.
engineResource.acquire();//engineResource引用计数自增
listener.onEngineJobComplete(this, key, engineResource);//通知Engine,EngineJob执行完毕,
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = cbs.size(); i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource, dataSource);//通知Request,数据获取Ready。
}
}
// Our request is complete, so we can release the resource.
engineResource.release();
release(false /*isRemovedFromQueue*/);
}
- Engine收到onEngineJobComplete的回调,根据缓存策略,将engineResource存入活跃缓存,并移除当前Job。自此,EngineJob完成使命,寿终正寝。
- 通知外部Request,回调其
onResourceReady()
,将Resource和Target关联起来。
总结
经过上面的流程分析,我们再回到开始的两个问题:
- Glide到底有几级缓存?这些缓存都是什么?
答: 这个问题是多方面的。从执行步骤上来说,算上SourceGenerator,一共有五级。分别是ActiveResource,LruResourceCache,ResourceCacheGenerator,DataCacheGenerator,SourceGenerator。如果从存储介质的角度来讲,就是三级:MEMORY_CACHE(内存缓存), DISK_CACHE(文件缓存),和原始数据(可能是本地或远端)。
- 当Glide获取资源后,处理资源的流程是什么样的?
根据最后一节的分析,基本是在DiskCacheStrategy的约束下,从SourceGenerator环节开始,依次回调,并进行对应层面的存储。最终设置给Target。