在上一篇文章中讲到Glide是如何通过RequestManager来管理资源加载请求,这篇文章就来看下资源加载请求执行和资源加载完成后显示的具体过程
《Glide源码分析——Request管理》
《Glide源码分析——开篇》
资源获取
资源获取的整体流程如下:
整个过程可概括为:
- 创建RequestBuilder,然后根据要显示在的目标和配置创建获取资源的请求(Request)
- 运行资源获取请求
创建获取资源请求
以RequestBuilder.into(ImageView imageView)为例,看下Reqeust创建的过程
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
BaseRequestOptions<?> requestOptions = this;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions = requestOptions.clone().optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions = requestOptions.clone().optionalFitCenter();
break;
case FIT_XY:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
null,
requestOptions,
Executors.mainThreadExecutor());
}
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
Request对象的创建是在buildRequest(target, targetListener, options, callbackExecutor) 完成的,这里不去追究具体的实现细节,主要理一下这个过程的逻辑,在将具体逻辑之前,我们先看下Request相关的类图 有关资源加载的几个类说明如下
-
Target:Glide加载资源后所显示位置的接口,最常见实现类是ImageViewTarget(持有ImageView对象引用)。Target的生命周期方法包括:onLoadStarted()、onResourceReady()、onLoadCleared()、onLoadFailed()。通常调用过程是: onLoadStarted -> onResourceReady/onLoadFailed -> onLoadCleared,但也不一定每次都如此,比如当资源在内存或者资源加载失败时onLoadStarted()就不会被调用。 -
Request :一个定义了资源请求的执行、停止、取消以及请求状态等方法的接口。它的实现类有SingleRequest、ErrorRequestCoordinator和ThumbnailRequestCoordinator。 -
RequestCoordinator:管理具有相同Target 的多个Request 的接口。 -
ThumbnailRequestCoordinator:同时实现了Request和RequestCoordinator接口,它用于管理两个独立的Request ,其中一个是加载缩略图的请求,另一个是加载全尺寸的图片的请求,而这两个请求都是SingleRequest对象。 -
ErrorRequestCoordinator:同样会管理两个Request ,其中一个Request 是图片加载请求(可能是SingleRequest对象也可能是ThumbnailRequestCoordinator对象),我们把它称为"主请求";另一个是当主请求加载失败时触发的请求,我们把它称为"容错请求"
创建Reuest逻辑如下:
- 判断是否有配置容错请求,如果有则创建ErrorRequestCorrdinatord对象
errorRequestCoordinator ,并把该对象赋值给变量parentCoordinator 。 - 创建主请求,如果有配置缩略图请求,则创建ThumbnailRequestCoordinator对象,并创建全尺寸和缩略图加载请求(都是SingleRequest对象),然后返回ThumbnailRequestCoordinator对象;如果没有配置缩略图请求,则创建加载全尺寸图片请求(SingleRequest对象),并返回该请求对象。
- 如果没有配置容错请求,则返回第二步创建的主请求。如果有,则创建容错请求对象,并将容错请求和主请求分别赋值给
ErrorRequestCoordinator.error 和ErrorRequestCoordinator.primary 。
配置容错请求的式:
Glide.with(this)
.load(main_url)
.error(Glide.with(this).
load(backup_url))
.placeholder(R.drawable.placeholder)
.fitCenter()
.into(mImvTest);
配置缩略图请求的方式:
Glide.with(this)
.load(main_url)
.thumbnail(Glide.with(this).
load(thumb_url))
.placeholder(R.drawable.placeholder)
.fitCenter()
.into(mImvTest);
Request创建情况
Request创建分为以下几种情况,其中顶层的节点为起始节点,也就是说Request执行的时候会以顶层的节点开始执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2vir2WOj-1641989986527)(./create_request_cases.png)]
a 没有配置缩略图和容错请求,对应的代码如下
Glide.with(this)
.load(main_url)
.placeholder(R.drawable.placeholder)
.fitCenter()
.into(mImvTest);
b 配置了缩略图请求,没有配置容错请求,对应代码如下
Glide.with(this)
.load(main_url)
.thumbnail(Glide.with(this).
load(thumb_url))
.placeholder(R.drawable.placeholder)
.fitCenter()
.into(mImvTest);
c 配置了容错请求,没有配置缩略图请求,对应代码如下
Glide.with(this)
.load(main_url)
.error(Glide.with(this).
load(backup_url))
.placeholder(R.drawable.placeholder)
.fitCenter()
.into(mImvTest);
d 配置了容错请求和缩略图请求,对应代码如下
Glide.with(this)
.load(main_url)
.error(Glide.with(this).
load(backup_url))
.thumbnail(Glide.with(this).
load(thumb_url))
.placeholder(R.drawable.placeholder)
.fitCenter()
.into(mImvTest);
运行资源获取请求
?在了解图片获取请求创建和类型之后,我们再看下执行图片加载请求的过程。对于不同类型的Request,执行的过程略有差异,比如ErrorRequestCorrdinatord会执行主请求,如果主请求执行失败则执行容错请求。因为实际的请求最终对应的都是SingleRequest对象,所以我们以最简单的情况(Request创建中的a情况)为例,看下执行图片加载请求的过程。从SingleRequest.begin() 方法开始执行资源加载请求,然后执行到Engine.load() 实现资源加载,流程可参考上面的时序图
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}
cb.onResourceReady(
memoryResource, DataSource.MEMORY_CACHE, false);
return null;
}
资源加载的逻辑如下:
从内存缓存中获取图片资源
@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
从内存中查找资源分为两种情况:一种是活动资源中查找;另一种是内存缓存中查找。先说第一种,活动资源实际上通过一个Map保存资源的弱引用,如果发生GC,所保存的资源会被回收。第二种内存缓存是一种采用LRU算法的缓存,通过LinkedHaspMap来保存资源。关于LRU算法,建议做下LeetCode上的LRU算法题,自己动手实现一遍。
从磁盘或者原始地址获取资源
如果在内存没有加载得到资源的话,会先从磁盘中获取,如果磁盘中没有加载到的话就则从资源的原始地址获取(网络、Assets、文件等)。在讲具体获取过程前,我们先来看下加载过程中涉及到的类,他们的关系图如下:
Engine.waitForExistingOrStartNewJob() 方法会执行一个DecodeJob任务,在该任务中实现从磁盘或者原始地址加载资源。看下几个关键的类和接口定义:
-
DecodeJob: 负责从缓存数据或原始地址获取资源然后解码。可以把它理解为一个加载资源的任务。 -
DecodeJob.RunReason: 定义了DeocdeJob被执行的原因,有初始状态(INITIALIZE)、从磁盘切换到原始资源(SWITCH_TO_SOURCE_SERVICE)、从其他线程获取到资源后回到原线程处理资源(DECODE_DATA)。 -
DecodeJob.Stage: 定义资源获取和转换的几阶段,包括:初始阶段(INITIALIZE)、从转码缓存获取资源阶段(RESOURCE_CACHE)、从未修改的数据缓存获取资源阶段(DATA_CACHE)、从原始路径获取资源阶段(SOURCE)、获取到资后源转码阶段(ENCODE)、结束阶段(FINISHED) -
DataFetcher: 泛型接口,定义了获取和取消资源获取的接口,有对应通过不同方式获取资源的实现类,比如网络(HttpUrlFetcher)、Assets(AssetPathFetcher)等。 -
ModelLoader: 用于将任意复杂的数据模型转换为具体数据类型的工厂接口, DataFetcher可以使用该数据类型来获取模型所表示的资源的数据。 -
DataFetcherGenerator: 作用是使用ModeLoader和数据模型生成一系列DataFetcher接口。DataFetcherGenerator实现类有:ResourceCacheGenerator(生成从转码后的缓存获取资源的DataFetcher)、DataCacheGenerator(生成从未修改的缓存获取资源的DataFetcher)、SourceGenerator(生成从原始地址获取资源的DataFetcher)
他们之间的关系可总结为:DecodeJob根据任务执行的原因(RunReason)和来源(Stage)创建DataFetcherGenerator接口实现类对象。DataFetcherGenerator接口实现类通过LoadData中的DataFetcher对象实现获取资源的功能。
class DecodeJob<R>
implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
@SuppressWarnings("PMD.AvoidRethrowingException")
@Override
public void run() {
GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", 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();
}
}
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);
}
}
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:
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
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);
}
}
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;
}
}
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}
@Override
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
}
具体加载过程在此不展开,感兴趣的可以去跟下源码。这里讲下从磁盘或者原始地址(网络、文件等)获取资源的逻辑,大致如下:
- 先从转码的磁盘缓存获取资源(通过ResourceCacheGenerator),如果有对应的资源则返回;如果没有,则从未修改的磁盘缓存获取资源(通过DataCacheGenerator)。
- 如果从未修改的磁盘缓存有获取到资源,则返回,否则从原始地址获取。
- 从原始地址获取到资源后,再执行DecodeJob任务,将从原始地址获取到的资源写入磁盘缓存(未经修改的资源缓存)。然后创建DataCacheGenerator对象,并执行从未修改的磁盘缓存中读取资源。
总结下资源加载的逻辑,入下图所示
最后附上资源加载的时序图仅供参考
资源显示
?资源显示的时序整体如下
从磁盘或者原始地址获取资源完成分为两种情况:一种是资源获取成功;另外一种是资源获取失败。对于这两种情况,显示的过程也不一样。
资源获取成功
?资源获取成功后DataFetcher接口实现类会最终会调用DecodeJob实现的DataFetcherGenerator.FetcherReadyCallback.onDataFetcherReady()方法,在该方法中会实现从磁盘读取缓存的文件并解码成Bitmap或者BimapDrawable,如果解码成功则显示解码后的内容;如果失败则显示占位符内容(优先级是:Fallback>Error>Placeholder)。
资源获取失败
? 获取资源失败的逻辑比较简单,如果获取资源失败则显示占位符内容(优先级是:Fallback>Error>Placeholder)。
对于从内存缓存中获取到资源的逻辑,它和从磁盘解码后显示的逻辑是一样的。
关于Request、Resource和Target的关系,可以参考下面的类图。Reuqest相当于连接Resource和Target的桥梁,当Resource加载完成后,Request负责将加载到的内容传给Target显示。
|