Glide源码学习笔记五:磁盘缓存
文章目录
前言
上一章Glide源码学习笔记四:内存二级缓存跟进到内存的二级缓存逻辑,这一章来分析没有从内存获取到缓存的情况下进行的操作。本文中的源文件统一指未处理的原图文件
先帮大家回忆一下上一章的逻辑:
public <R> LoadStatus load(......) {
......
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
//没有获取到内存缓存,开始新的逻辑
if (memoryResource == null) {
return waitForExistingOrStartNewJob(......)
}
}
......
return null;
}
当没有获取到内存缓存的时候,调用了waitForExistingOrStartNewJob方法,从命名可以很好理解:等待正在进行的加载或者开始一个新的加载任务。
接下来进入waitForExistingOrStartNewJob的学习:
waitForExistingOrStartNewJob()
private <R> LoadStatus waitForExistingOrStartNewJob(......) {
//Jobs中使用两个hashMap来保存EngineJob
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
//如果之前已经开始过这个加载任务,就重新添加一次监听,等待这个任务的完成
if (current != null) {
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
//创建EngineJob
EngineJob<R> engineJob =
engineJobFactory.build(......);
//创建DecodeJob
DecodeJob<R> decodeJob =
decodeJobFactory.build(......);
//将EngineJob保存起来,避免重复执行相同的加载任务
jobs.put(key, engineJob);
//添加监听
engineJob.addCallback(cb, callbackExecutor);
//开始任务
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
上述代码中有几个关键类:
1.Jobs:保存已经执行的EngineJob对象,避免创建多个重复的EngineJob。内部是由两个HashMap构成。onlyCacheJobs和jobs,这两个HashMap是互斥关系,当设置onlyRetrieveFromCache=true使用onlyCacheJobs,反之使用jobs。这样处理有什么好处呢?
2.DecodeJob:从初始资源或者缓存中获取解码资源,将这些资源转换和解码,实现了Runnable。
3.EngineJob:当资源准备完毕,通过回调通知。通过添加回调或者移除回调管理加载。
于是waitForExistingOrStartNewJob这个方法做的事情就很好理解了:
一个加载请求对应一个EngineJob,EngineJob实现了很多回调,真正处理图片请求的是DecodeJob,加载任务的执行不同情况回调不同的方法,在这些回调方法中处理各种逻辑。
接下来分析DecodeJob的run方法,看看是如何处理资源的:
DecodeJob.run()
@Override
public void run() {
GlideTrace.beginSectionFormat("DecodeJob#run(reason=%s, model=%s)", runReason, model);
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (CallbackException e) {
throw e;
} catch (Throwable t) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(
TAG,
"DecodeJob threw unexpectedly" + ", isCancelled: " + isCancelled + ", stage: " + stage,
t);
}
if (stage != Stage.ENCODE) {
throwables.add(t);
notifyFailed();
}
if (!isCancelled) {
throw t;
}
throw t;
} finally {
if (localFetcher != null) {
localFetcher.cleanup();
}
GlideTrace.endSection();
}
}
乍看之下,执行了runWrapped()方法,catch了一些异常。直接进入runWrapped()方法。
runWrapped()
分析runWrapped方法前,先看两个枚举:
/** Why we're being executed again. */
private enum RunReason {
/** The first time we've been submitted. */
INITIALIZE,
/** We want to switch from the disk cache service to the source executor. */
SWITCH_TO_SOURCE_SERVICE,
/**
* We retrieved some data on a thread we don't own and want to switch back to our thread to
* process the data.
*/
DECODE_DATA,
}
RunReason :字面意思就是这个runnable为什么要再一次run,也就是说,成员变量RunReason根据状态改变,
在这里枚举了三种情况:
INITIALIZE:第一次提交一个Job,也就是刚刚engineJob.start(decodeJob)的情况。
SWITCH_TO_SOURCE_SERVICE:不从磁盘缓存获取资源了,直接获取源文件。
DECODE_DATA:在非当前线程获取到了资源,想切换到当前线程更新数据。
按照之前的线索,只能理解INITIALIZE是怎么回事,先直接翻译注释简单理解一下,知道大概怎么回事,带着困惑进行下面的分析,食用更香!
再看看Stage :
/** Where we're trying to decode data from. */
private enum Stage {
/** The initial stage. */
INITIALIZE,
/** Decode from a cached resource. */
RESOURCE_CACHE,
/** Decode from cached source data. */
DATA_CACHE,
/** Decode from retrieved source. */
SOURCE,
/** Encoding transformed resources after a successful load. */
ENCODE,
/** No more viable stages. */
FINISHED,
}
Stage:这个是较好理解,解码的数据来源,和RunReason 一样也有一个Stage成员变量存储该枚举值,那这个阶段是什么阶段呢?
INITIALIZE:初始化阶段。
RESOURCE_CACHE:解码的数据来源是缓存中处理过的文件。这里就不是内存缓存了。因为内存缓存获取到了,就不会走到这里来,在上一章就已经结束了加载流程。那这个缓存肯定就是磁盘缓存了。
DATA_CACHE:解码数据来源是未处理过的源数据,这里先理解为从网络上download下来未处理的源数据缓存。
SOURCE:解码数据来源是源文件。
ENCODE:在成功下载资源并且缩放后,开始编码到本地。
FINISHED:解码这一阶段的流程结束。
了解这些枚举值代表的含义,接着看runWrapped()方法就会更加容易理解了。
private void runWrapped() {
switch (runReason) {
//一开始触发DecodeJob的情况
case INITIALIZE:
//这里给stage赋值到下一个阶段
stage = getNextStage(Stage.INITIALIZE);
//currentGenerator是DataFetcherGenerator类型的,在DecodeJob的构造方法中默认没有赋值,这是第一次赋值
currentGenerator = getNextGenerator();
//这个方法下面再看
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
//这个方法下面再看
decodeFromRetrievedData();
break;
default:
//非正常情况,抛出异常
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
从代码中可以很明显看出,runWrapped()就是根据runReason来确定stage,不同的stage对应不同的逻辑。
接下来依次分析getNextStage(),getNextGenerator(),runGenerators(),decodeFromRetrievedData()。
getNextStage()
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
//如果缓存策略支持解码已缓存的资源,设置stage为RESOURCE_CACHE类型,若不支持,递归该方法,再次获取stage
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE
: getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
//如果缓存策略支持解码已缓存的源文件,设置stage为DATA_CACHE类型,若不支持,递归该方法,再次获取stage
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE
: getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
//如果设置了只能从缓存中读取,设置stage为FINISHED否则设置为SOURCE去获取源文件
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
diskCacheStrategy这个东西应该很熟悉了,缓存策略。diskCacheStrategy的一些属性很好理解,在这里就不解释了,可以自己打开源码看一看。总而言之,getNextStage方法就是根据不同缓存策略设置不同的stage。
其实看到了这里,磁盘的缓存策略已经很清晰了:
1.先去磁盘缓存中寻找已经处理好了资源,没有找到执行2
2.寻找缓存中的源文件,没有找到执行3
3.直接获取源文件,没有获取到,肯定就去执行下载操作了
接下来分析getNextGenerator()。
getNextGenerator()
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
逻辑很清晰,已经获取到当前情况的stage,接下来就是根据stage执行不同的磁盘资源获取方式。
runGenerators()
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled
&& currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
// Otherwise a generator started a new load and we expect to be called back in
// onDataFetcherReady.
}
执行runGenerators方法,内部有一个while循环,跳出循环由两种情况,第一种,currentGenerator.startNext()为true,该方法设计到了缓存获取以及下载流程,放在下一章考虑。第二种就是stage == Stage.SOURCE,执行了reschedule()方法,来看看:
该方法
reschedule()
@Override
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
//回调,在EngineJob的reschedule处理
callback.reschedule(this);
}
@Override
public void reschedule(DecodeJob<?> job) {
// Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
// up.
//重新开始这个任务
getActiveSourceExecutor().execute(job);
}
这其实就是处理异常了,磁盘缓存未获取到资源,后续操作也没有获取到资源,于是重新开始该任务。
decodeFromRetrievedData()
当runReason改变为DECODE_DATA,也就是可以获取到资源,需要回调处理资源了。
private void decodeFromRetrievedData() {
......
Resource<R> resource = null;
try {
//获取资源
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
//
notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
} else {
runGenerators();
}
}
这个回调链太长了,最终会回调到Engine的onEngineJobComplete()方法,看看有什么惊喜:
Engine.onEngineJobComplete()
@SuppressWarnings("unchecked")
@Override
public synchronized void onEngineJobComplete(
EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null && resource.isMemoryCacheable()) {
activeResources.activate(key, resource);
}
jobs.removeIfCurrent(key, engineJob);
}
看到这个activeResources是不是异常熟悉?这个就是上一章内存缓存的第一级缓存。这样,内存缓存与磁盘缓存就打通了。
总结
本文主要从源码角度分析了磁盘缓存的策略。若大家有不同意见,欢迎讨论,一起学习一起进步!