Glide系列第一篇最后我说下一期要探索Glide中的缓存策略,但是在今天这个内容中我不得不食言了。原以为Glide初始化之后,除了缓存策略就没有太多有趣的点值得探讨,但是深入一看,确实有一些值得我们学习的思想内容,所以,今天这篇的内容就像题目描述的那样,我们一起探索下Glide中图片通过Request请求的设计方式。
还是以一句代码为例,我们先概括Glide在执行过程中存在的三个阶段。
Glide.with(context).load(url).asBitmap().into(imageView)
- 第一步:
Glide.with(context)
Glide的初始化; - 第二步:
asBitmap().load(url)
参数配置,包括资源来源,资源的类型等信息; - 第三部:
into(imageView)
,真正将之前设置的参数应用于图片载体Target的构建,图片的请求,缓存策略的使用等。
上一篇文章《详解 Glide框架(一):Glide的初始化流程》中我们详细介绍了第一步,包括其单例的初始化,以及选择合适的Context作为生命周期的管理容器等内容。接下来,我们会简单介绍下第二步的内容,真正要重点了解的其实是第三步中的操作。
从RequestManager到RequestBuilder
经过Glide.with()的一顿操作之后,我们得到了RequestManager这个类。 RequestManager 对外暴露的最主要能力之一,就是创建合适资源类型的RequestBuilder,如asBitmap()会得到 RequestBuilder< Bitmap >。
public RequestBuilder<Bitmap> asBitmap() {
return as(Bitmap.class).apply(DECODE_TYPE_BITMAP);
}
public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass);
//构造方法中,this指代的就是RequestManager,注意,它并不是单例,而是与RequestManagerFragment相关联的实例。
//泛型resourceClass则是举例中的Bitmap.class。同理,如果调用的是asGif或asFile,则是对应的class类型
}
当然,asXXX
方法是获取RequestBuilder的起始方法,只有asXXX调用之后我们才能够唯一确定resourceType
到底是什么类型。因此我们得知,asXXX和load()等方法的顺序是不能颠倒的。从源码也可以看出,如果没有asXXX()的调用,则默认为asDrawable()
方法,这里就不贴了。
下面分析RequestBuilder< Bitmap >的执行内容。
说实话,建造者模式的RequestBuilder,其方法并不是多,而且大多数是重载方法。其中包含我们最常用的load()
和into()
。
load()的众多重载方法最后都执行loadGeneric():
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
作为建造者模式的典型行为,它记录了传入的图片源信息,并设置信息源标记位。
最后一步,则是调用into(),开始最后的流程。
into(),最后的冲刺
into()这个方法有点像典型的建造者模式中的最后一步,build()。我们暂且可以这么理解。对于into(imageView)来说,最后会调用以下这个方法:
private <Y extends Target<TranscodeType>> Y into(@NonNull Y target, RequestOptions options) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {//检查图片源信息是否设置
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
options = options.autoClone();
//第1件事儿
Request request = buildRequest(target, options);
Request previous = target.getRequest();
//第2件事儿
if (request.isEquivalentTo(previous)) {
request.recycle();
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
//第3件事儿
requestManager.clear(target);
target.setRequest(request);
//第4件事儿
requestManager.track(target, request);
return target;
}
into的整体过程做了四件事儿:
- 获得图片请求的Request,建造或者复用
- 若拿到的request是复用的,则检查request状态,若没有在执行,则重新执行,
previous.begin()
,否则(处于执行中),就当无事发生,返回target,说明正处于最后阶段的执行过程中。 - 若2条件不满足,request无法复用,则用requestManager清理target的请求,重新设置新的请求
target.setRequest(request)
- 通过requestManager启动新的请求并监控。
建造Request
在开篇举例的参数设置情况下,target就是ImageView,requestOption走的是default。我们没有在构建参数的时候调用thumbnail( @Nullable RequestBuilder<TranscodeType> thumbnailRequest)
或thumbnail(float sizeMultiplier)
, 也就不存在缩略图相关请求。那么最终调用到buildRequest流程就比较简单。如下是核心代码:
private Request buildRequest(Target<TranscodeType> target, RequestOptions requestOptions) {
return buildRequestRecursive(target, null, transitionOptions, requestOptions.getPriority(),
requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight(), requestOptions);
}
private Request buildRequestRecursive(Target<TranscodeType> target,
@Nullable ThumbnailRequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority, int overrideWidth, int overrideHeight, RequestOptions requestOptions) {
if (thumbnailBuilder != null) { //举例中我们没有设置缩略图请求,因此不走该分支。
// Recursive case: contains a potentially recursive thumbnail request builder.
if (isThumbnailBuilt) {
throw new IllegalStateException("You cannot use a request as both the main request and a "
+ "thumbnail, consider using clone() on the request(s) passed to thumbnail()");
}
TransitionOptions<?, ? super TranscodeType> thumbTransitionOptions =
thumbnailBuilder.transitionOptions;
// Apply our transition by default to thumbnail requests but avoid overriding custom options
// that may have been applied on the thumbnail request explicitly.
if (thumbnailBuilder.isDefaultTransitionOptionsSet) {
thumbTransitionOptions = transitionOptions;
}
Priority thumbPriority = thumbnailBuilder.requestOptions.isPrioritySet()
? thumbnailBuilder.requestOptions.getPriority() : getThumbnailPriority(priority);
int thumbOverrideWidth = thumbnailBuilder.requestOptions.getOverrideWidth();
int thumbOverrideHeight = thumbnailBuilder.requestOptions.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight)
&& !thumbnailBuilder.requestOptions.isValidOverride()) {
thumbOverrideWidth = requestOptions.getOverrideWidth();
thumbOverrideHeight = requestOptions.getOverrideHeight();
}
ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
Request fullRequest = obtainRequest(target, requestOptions, coordinator,
transitionOptions, priority, overrideWidth, overrideHeight);
isThumbnailBuilt = true;
// Recursively generate thumbnail requests.
Request thumbRequest =
thumbnailBuilder.buildRequestRecursive(
target,
coordinator,
thumbTransitionOptions,
thumbPriority,
thumbOverrideWidth,
thumbOverrideHeight,
thumbnailBuilder.requestOptions);
isThumbnailBuilt = false;
coordinator.setRequests(fullRequest, thumbRequest);
return coordinator;
} else if (thumbSizeMultiplier != null) {//举例中我们没有设置缩略图重载方法,因此也不走该分支。
// Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
Request fullRequest = obtainRequest(target, requestOptions, coordinator, transitionOptions,
priority, overrideWidth, overrideHeight);
RequestOptions thumbnailOptions = requestOptions.clone()
.sizeMultiplier(thumbSizeMultiplier);
Request thumbnailRequest = obtainRequest(target, thumbnailOptions, coordinator,
transitionOptions, getThumbnailPriority(priority), overrideWidth, overrideHeight);
coordinator.setRequests(fullRequest, thumbnailRequest);
return coordinator;
} else {//最基本的情况分支:无缩略图流程。
// Base case: no thumbnail.
return obtainRequest(target, requestOptions, parentCoordinator, transitionOptions, priority,
overrideWidth, overrideHeight);
}
}
用尾递归的方式构建Request执行链
由于举例的行为过于单一,我们忽略了以上方法中的缩略图请求处理,也就相当于将整个递归加载的方式转换为了“只有一层的递归调用”。但在诸多的应用场景中,尤其是toC
的很多场景,为了用户体验,我们需要尽快展示出缩略图,然后在替换为原图。不得不说,从官方注释也好,从方法命名上看也好,这个分支的水都是挺深的。不要以为这个分支逻辑就单单是通过一个叫ThumbnailRequestCoordinator
的类来管理thumbnail和full两个图片的请求之间的协调。频繁出现的Recursive:递归
就已经说明它在逻辑上的复杂度了。
因此我们要以递归的思想再去细看buildRequestRecursive()
方法。这个方法抽象一下,其实就是以下伪代码逻辑(你品,你细品):
private fun buildRequestBuilderRecursive() {
if(hasInnerRequestBuilder()) {//这个说的就是通过thumbnail()设置进来的RequestBuilder。
val innerRequestBuilder = getinnerRequestBuilder()
innerRequestBuilder.buildRequestBuilderRecursive()
} else {
buildSingleRequest()
}
}
在源码中,Request thumbRequest = thumbnailBuilder.buildRequestRecursive(target, coordinator,...)
这句的意思,是说处于当前的RequestBuild中,要想构建这一层的coordinator,就必须先构建好它递归下层的request。因此从request的构建顺序来看,是由内向外依次构建,最终返回最外层的Request(可能是一个SingleRequest,也可能是个Coordinator,如上面类图)。而递归最深处的那个Request,一定是一个SingleRequest,因为它走到了终止条件:
return obtainRequest(target, requestOptions, parentCoordinator, transitionOptions, priority,overrideWidth, overrideHeight);
到此,我们先不追究obtainRequest中的细节,我们整体的Request构建流程已经完成。
通过Target绑定Request
通过into(imageView)
传入的ImageView,会通过ImageViewFactory.build()方法,根据as()制定的图像类型(这里是Bitmap)生成对应的ImageViewTarget对象,并内部持有ImageView。
public class ImageViewTargetFactory {
@SuppressWarnings("unchecked")
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {//根据不同的图片类型,生成不同的Target对象
return (Target<Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (Target<Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
}
之后再调用target.setRequest()的过程中,其实是调用了ImageView.setTag(request)方法,将request直接作为Tag塞入了View中。
个人觉得这应该算是一个设计上的失误。曾经在使用的过程中就因为已经占用了View的tag,导致了使用Glide加载的时候崩溃。
启动Request链
用首递归的方式启动加载,以短路的方式完成替换
从buildRequestRecursive()
方法源码中,我们会发现一个代理类:ThumbnailRequestCoordinator
。ThumbnailRequestCoordinator对象来说,thumb和full这两个Request是成对儿存在的。毕竟它的作用就是来协调他们两个的请求过程的。而它所持有的coordinator,来自递归的上一层,存在“parent”的含义(果不其然,在4.11.0版本中,该变量被命名为了parent),是用于执行短路逻辑的。ThumbnailRequestCoordinator类图如下所示:
因此从Request的角度来看,无论是SingleRequest还是ThumbnailRequestCoordinator,它都具备请求的能力。结合
into()
方法中的第四件事,我们可以了解到它的请求顺序:
第四件事儿:requestManager.track(target, request);
public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();//主流程
} else {
pendingRequests.add(request);
}
}
在注释了“主流程”的这一行,真正的请求行动开始了。这里的request,是最外层的Request,它所持有的full,就是最终要展示的图片。接下来我们向里看:
@Override
public void begin() {
isRunning = true;
if (!thumb.isRunning()) {
thumb.begin();
}
if (isRunning && !full.isRunning()) {
full.begin();
}
}
Request的启动是同时进行的,当前层的thumb则作为更深一层的full使用,而深一层又有自己的thumb。各个请求以首递归的方式依次启动。在这样的启动流程李,不免会生出一个巨大的疑问:既然最外层Request的full才是最终我们要展示的图片资源,而所有Request又是同时启动的,那如果full先于所有的深层thumbnail请求得到,该怎么办? 一起看下ThumbnailRequestCoordinator中的另外两个方法:
@Override
public boolean canSetImage(Request request) {//判断传入的request是否有资格设置给Target。
return parentCanSetImage() && (request.equals(full) || !full.isResourceSet());
}
private boolean parentCanSetImage() {//判断上层节点是否有资格
return coordinator == null || coordinator.canSetImage(this);
}
这两个方法说的其实就是在递归过程中,如何判断自身节点的有效性。持有上层节点的引用,能够首先调用上层节点的有效性,若parentCanSetImage,则再判断自身的有效性。因为越外层的Request获取的结果,优先级越高,因此,若上层都不具备设置图片的资格,那就说明,具备更高优先级的图片已经被设置进来,当前这条递归线路就到此为止,即便自身请求成功,这个资源也用不上了,因此后面还有释放资源的逻辑。这就是我们说的短路
。
最后,为了加深理解,提供这样一个例子,感兴趣的同学可以尝试下看看效果(为了调用明显起见,将三个requestBuilder加载的图片设置为不同的网络图片):
val requestBuilder2 = Glide.with(this).asBitmap()
.load("http://img02.tooopen.com/images/20160509/tooopen_sy_161967094653.jpg")
val requestBuilder1 = Glide.with(this).asBitmap()
.load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1602580000631&di=1d3ef270d62405a618a96651f5a48cc7&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D4132727200%2C2237246567%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D582")
.thumbnail(requestBuilder2)
Glide.with(this).asBitmap()
.load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1602579996309&di=15976afa6b07e6f54343351255fa2432&imgtype=0&src=http%3A%2F%2Ft9.baidu.com%2Fit%2Fu%3D1307125826%2C3433407105%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D5760%26h%3D3240")
.thumbnail(requestBuilder1)
.apply(RequestOptions.placeholderOf(R.drawable.thumbnail_bg))
.into(iv)
在初次(记得排除缓存策略的干扰哦)运行的过程中,iv控件的图片加载顺序为:R.drawable.thumbnail_bg->requestBuilder2->requestBuilder1->最终图。
总结
文章中我们分析了在完全初始化状态之后,首次运行时,Glide框架组织Request责任链的全流程。总结重点如下:
- RequestBuilder通过尾递归的方式建立Request执行链。
- 在启动过程中,Request链通过首递归的顺序启动,并通过持有上一层节点的方式进行短路判断。
最后,下一篇文章,我们一定会分析Glide中的又一个重量级核心类:Engine
,和它所用到的所有缓存策略。