Glide 中的DecodeJob 得工作其实是比较乱的,看的我真是不要不要的,真想说 read the f**king source code
今天继续昨天的DecodeJob 来分析,
先来捋一捋整个图片第一次加载流程,这里我们将它分为4步
1.根据当前的url 宽 高 还有其他信息,去查找已经在磁盘上面按照图片的宽高缓存好的图片,并一层一层向上返回
2.如果按照宽高没有找到,则尝试寻找没有宽高的原图,如果存在原图,那么将这个原图按照宽高获取,并一层一层向上返回
3.如果原图也没有,则去下载这个图片,
4.图片下载完后,根据是否可以在磁盘缓存,如果可以则执行第2步,否则直接向上返回
工作流程已经知道了,继续看一下关键的类
EngineJob
EngineJob 可以作为桥梁通知上层接口,同时还可以作为线程选择的调度
ResourceCacheGenerator
ResourceCacheGenerator 是一个根据指定的宽高去加载磁盘缓存的工具类,
DataCacheGenerator
DataCacheGenerator 是一个根据原图去加载指定宽高的工具类
SourceGenerator
SourceGenerator是一个处理缓存和开启下载图片的工具类
DecodeHelper
DecodeHelper 是 一个帮助我们获取一些信息和实施下载的工具类
知道了这些再去看代码会轻松的很多,
class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback, Runnable,Comparable>,Poolable
先看一下DecodeJob 的类的继承关系,需要注意的是他实现了Runnable 接口,这样我们就知道改如何他看的内部方法的执行顺序了,还是从上一篇的构建DecodeJob开始分析
EngineJob engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob 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);
从代码上看到创建了一个DecodeJob ,并使用engineJob.start ,
EngineJob.start
public synchronized void start(DecodeJob decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
看到他做了一个判断,用来选择线程池来执行DecodeJob ,从字面的意思应该是配置有关系,意思应该是是否可以从缓存解码,如果可以则使用磁盘缓存线程池,否则选择下载线程池,既然是执行DecodeJob 这个Runnable ,那么直接去看一下他的Runnable
DecodeJob.run
@Override
public void run() {
// This should be much more fine grained, but since Java's thread pool implementation silently
// swallows all otherwise fatal exceptions, this will at least make it obvious to developers
// that something is failing.
GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);
// Methods in the try statement can invalidate currentFetcher, so set a local variable here to
// ensure that the fetcher is cleaned up either way.
DataFetcher> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (CallbackException e) {
// If a callback not controlled by Glide throws an exception, we should avoid the Glide
// specific debug logic below.
throw e;
} catch (Throwable t) {
// Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our
// usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We
// are however ensuring that our callbacks are always notified when a load fails. Without this
// notification, uncaught throwables never notify the corresponding callbacks, which can cause
// loads to silently hang forever, a case that's especially bad for users using Futures on
// background threads.
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "DecodeJob threw unexpectedly"
+ ", isCancelled: " + isCancelled
+ ", stage: " + stage, t);
}
// When we're encoding we've already notified our callback and it isn't safe to do so again.
if (stage != Stage.ENCODE) {
throwables.add(t);
notifyFailed();
}
if (!isCancelled) {
throw t;
}
throw t;
} finally {
// Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call
// close in all cases anyway.
if (localFetcher != null) {
localFetcher.cleanup();
}
GlideTrace.endSection();
}
}
在run方法中先判断了是否已经取消此次请求,其实我在最开始阅读的时候在这里就有一个疑问,既然加载图片,那么在开始加载前就判断是否取消有什么意义,难道写代码的时候Glide.with(context).load(url).into(imageView),然后下一行代码直接取消?否则最开始判断取消的意义又在哪里,这个问题我们先记一下,后面我们在分析代码的过程中会解答
继续分析run 方法,判断了是否取消后就执行了 runWrapped(); 方法
不过这里我们需要定义一下,此次加载是我么首次加载这张图片,不存在内存缓存,也不存在磁盘缓存,
DecodeJob.runWrapped
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
可以看到他是根据状态来判断执行的哪一个方法,DecodeJob 在初始化的时候将状态设置为INITIALIZE,那么此时我们就INITIALIZE分支,记住现在的状态
状态 INITIALIZE状态
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
在INITIALIZE 状态下执行了上面的方法 ,去看getNextStage(Stage.INITIALIZE);
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// 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 来判断是否使用磁盘缓存宽高缓存,这个是在构建Glide时配置的缓存设置,默认使用DiskCacheStrategy.AUTOMATIC ,返回的是true
DiskCacheStrategy.AUTOMATIC
public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return dataSource == DataSource.REMOTE;
}
@Override
public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
EncodeStrategy encodeStrategy) {
return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
|| dataSource == DataSource.LOCAL)
&& encodeStrategy == EncodeStrategy.TRANSFORMED;
}
@Override
public boolean decodeCachedResource() {
return true;
}
@Override
public boolean decodeCachedData() {
return true;
}
};
此时状态变更为RESOURCE_CACHE ,继续执行getNextGenerator 方法
状态 RESOURCE_CACHE
DecodeJob.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);
}
}
返回的是一个ResourceCacheGenerator,并将它赋值给currentGenerator ,然后执行runGenerators方法
DecodeJob.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.
}
这里的判断就比较抽象了,
此时就执行了我们上面所描述的第1步
没有被取消,currentGenerator 不为空,在getNextGenerator,我们得到的了一个ResourceCacheGenerator,所以这个条件也成立,currentGenerator.startNext() 的意思就是是否找到有相应宽高的磁盘缓存,首次加载肯定是没有的,那么这个条件的否定命题也是成立的,
执行完第1步后更改状态,执行第2步
当前状态RESOURCE_CACHE,此时就会再次更换状态stage = getNextStage(stage);将状态变更为DATA_CACHE,同时也会判断是否可以保存原图,在默认的DiskCacheStrategy.AUTOMATIC 配置中是true,此时 currentGenerator = getNextGenerator();返回了一个DataCacheGenerator,由于是第一次加载所以这里currentGenerator.startNext 返回同样是false,继续执行whild循环
同理执行第3步,
当前状态为DATA_CACHE,我们同样还是通过stage = getNextStage(stage);将状态变更为SOURCE,将currentGenerator变更为SourceGenerator,此时
if (stage == Stage.SOURCE) {
reschedule();
return;
}
当前状态变更到了SOURCE,跳出循环,执行reschedule
DecodeJob.reschedule
@Override
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
在reschedule 方法中将runReason 变更为 SWITCH_TO_SOURCE_SERVICE,同时调用callback.reschedule 方法,在DecodeJob的初始化方法中发现这个callback就是 EngineJob,我去看一下他的reschedule方法
EngineJob.reschedule
@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);
}
这里就比较简单了,重新选择一个线程池来执行DecodeJob 这个Runnable,同样也解释了我们在上面没有说清楚两个问题,
1.在engineJob.start(decodeJob);中我们说他是根据一个配置来选择所需的线程池,从上面的代码其实是可以理解的,磁盘缓存和下载是在不同的线程池中工作的,
2.那就是为什么在run 方法的最开始就判断是否已经取消加载图片,原因就是存在线程的调度,这个run方法不一定只执行一次
调用完线程后,将会重新执行DecodeJob.run 方法,我们先标记一下状态
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
stage=SOURCE
currentGenerator=SourceGenerator
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
此时再执行run方法后 就会执行runWrapped 的SWITCH_TO_SOURCE_SERVICE 分支,执行 runGenerators 方法,此时就会执行SourceGenerator的下载方法,看到这里想必整个加载的流程大家心里面已经有一个差不多的概念了,
这个里面涉及到了三个关键的类 ResourceCacheGenerator DataCacheGenerator SourceGenerator 他们的具体工作流程,我们来看一下,主要是看他们的startNext 的方法
ResourceCacheGenerator.startNext
@Override
public boolean startNext() {
List sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
List> resourceClasses = helper.getRegisteredResourceClasses();
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
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 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 这个类的表述,其中还涉及到DecodeHelper的一些使用,我也是从网上看了一些文章,https://blog.csdn.net/nbsp22/article/details/80651648 这一篇文章讲的就非常不错,还有这个逻辑虽然我能看懂,但是让我描述出来就有点绕了,我也从他的文章中copy 一下他的描述
此时通过decodeHelper拿到的sourceIds就是[GlideUrl,ObjectKey],如果通过注册的信息找不到此时的key,表明glide本身还不支持这种方式,因此调用结束。显然此时是支持的,接下来是通过helper的getRegisteredResourceClasses获取resourceClass信息,这里大致是glide所支持的资源类信息,也就是能够进行decode的。这里它存放的是[GifDrawable,Bitmap,BitmapDrawable]。因此接下来进入第一个的while循环:
1.由resourceClasses和sourceIds组成的一个正交关系,迭代每一组。
2.迭代开始前,若modelLoaders为空或者size为0,则继续迭代进入步骤3,否则循环结束。
3/循环中,检测是否已经全部迭代完成,如果还有,则进入步骤4,否则循环结束。
4.对每一组,获取相应的缓存Key对象,根据缓存key去diskcache中查找缓存文件,查找成功,则通过getModelLoaders获取当前的modelLoaders信息,继续执行循环,进入步骤2。
从这里我们可以看出这个while循环的作用就是找到modelLoaders信息,如果没找到有效的,则循环结束,方法块正交组迭代完成之后,startNext方法结束,方法返回false,交给下一个Generator去处理。如果能够找到,则执行下一个while循环。这个循环相对简单一些,就是根据上一个while循环查找到的modelLoaders,进行遍历,只要有一个对应的fetcher能够处理,则startNext返回true,表明此时这个generator已经能够处理本次请求,所以也不会再交给其他的generator对应的fetcher去处理了。
在我们此时的情景中,ResourceCacheGenerator是无法处理本次请求的,所以,交给下一个Generator去处理,也就是DataCacheGenerator的startNext。
DataCacheGenerator .startNext
@Override
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 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的描述,那么再看这里就比较简单了,我们来说一下他们之间有什么不同,在ResourceCacheGenerator 与 DataCacheGenerator 都是利用key 从缓存中匹配的,但是在ResourceCacheGenerator 中的key的创建利用了宽高而DataCacheGenerator 中则没有,可见ResourceCacheGenerator 是获取的是响应宽高的资源,而DataCacheGenerator 是获取的原图,在获取原图成功后再根据宽高去获取相应数据,
第一次加载这里肯定也是没有的,继续去下载
SourceGenerator .startNext
@Override
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++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
首次执行 startNext 方法中的dataToCache 肯定是null,所有不会走加载缓存的方法,而是进入while中执行循环下载图片,具体的下载过程是在loadData.fetcher.loadData(helper.getPriority(), this);这个方法中执行的,我们就不分析了,来说一下下载后他干了什么,下载成功后会回调onDataReady 这个方法
SourceGenerator.onDataReady
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
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 {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
判断了一下是否可以磁盘换粗,如果可以将dataToCache 设置为data用于数据缓存,继续执行线程调度,我们在上面分析过了,如果线程调度之后肯定还是会走SourceGenerator.startNext 方法,此时dataToCache 不为null,
SourceGenerator.startNext
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
这里是走了缓存数据,然后创建一个DataCacheGenerator(原始图片解码器) 去执行他的解码,
SourceGenerator.cacheData
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
Encoder
到这里整个过程就结束了