Glide 源码学习 01:从 into(ImageView) 走到 SingleRequest.begin()
Glide.with(imageView).load(url).into(imageView)进入请求启动前半段:RequestBuilder.into(ImageView)->into(Target)->buildRequest->SingleRequest.obtain->SingleRequest.begin()。后续的Engine.load()、缓存、DecodeJob 另起章节继续完整拆。
源码版本:Glide 4.16.0。
0. 本篇入口
日常写法:
kotlin
Glide.with(imageView)
.load(url)
.into(imageView)
这行代码在调用链里会落到 RequestBuilder.into(ImageView)。这里不是直接开始网络请求,而是先处理主线程、ImageView 的 ScaleType、Target 创建、旧请求复用/清理、Request 构建和 RequestManager 追踪。
1. RequestBuilder.into(ImageView):先处理 ImageView 特有逻辑
源码:
java
@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) {
// Clone in this method so that if we use this RequestBuilder to load into a View and then
// into a different target, we don't retain the transformation applied based on the previous
// View's scale type.
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:
// Do nothing.
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/* targetListener= */ null,
requestOptions,
Executors.mainThreadExecutor());
}
逐段解释:
java
Util.assertMainThread();
into(ImageView) 必须在主线程调用。原因不是"网络请求在主线程执行",而是这里会读写 View、设置 placeholder、保存 Request 到 View tag、后续回调 Target,这些 UI 操作都必须在主线程。
java
Preconditions.checkNotNull(view);
如果传入空 ImageView,直接抛异常。Glide 不会帮你静默忽略,因为后面必须通过这个 View 构建 Target。
java
BaseRequestOptions<?> requestOptions = this;
RequestBuilder 本身继承自 BaseRequestOptions,所以当前 builder 也可以当成本次请求的 options 使用。
java
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
这段判断说明:只有当用户没有显式设置 transformation、并且当前请求允许 transformation、并且 ImageView 有 ScaleType 时,Glide 才会根据 ImageView 的 ScaleType 自动补一个可选 transformation。
面试重点:如果用户已经手动设置了 centerCrop()、fitCenter()、transform(),Glide 不会再根据 ImageView.ScaleType 覆盖用户配置。
java
requestOptions = requestOptions.clone().optionalCenterCrop();
源码注释已经说明为什么要 clone():同一个 RequestBuilder 可能被复用到不同 ImageView。如果直接修改原始 builder,那么第一个 ImageView 的 ScaleType 会污染后续请求。
ScaleType 到 Transformation 的映射:
text
CENTER_CROP -> optionalCenterCrop()
CENTER_INSIDE -> optionalCenterInside()
FIT_CENTER / FIT_START / FIT_END -> optionalFitCenter()
FIT_XY -> optionalCenterInside()
CENTER / MATRIX -> 不处理
最后:
java
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/* targetListener= */ null,
requestOptions,
Executors.mainThreadExecutor());
这里发生了四件事:
ImageView被包装成ViewTarget<ImageView, TranscodeType>。targetListener为 null,说明没有额外给 Target 绑定单独 listener。- 传入刚才处理过 ScaleType 的
requestOptions。 - callback executor 使用主线程执行器,后续资源 ready 后回调到 UI。
2. into(Target):构建 Request、处理旧请求、交给 RequestManager
上一步会进入私有重载:
java
<Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
Executor callbackExecutor) {
return into(target, targetListener, /* options= */ this, callbackExecutor);
}
这个公开/包内重载只是把当前 RequestBuilder 自己作为 options 传进去。ImageView 那条路径会直接调用下面这个私有方法:
java
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 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()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
逐段解释:
java
Preconditions.checkNotNull(target);
Target 不能为空。对 into(ImageView) 来说,Target 是前面完整源码里这一行创建出来的 ViewTarget:
java
glideContext.buildImageViewTarget(view, transcodeClass)
java
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
必须先调用 load()。load() 的核心作用之一是设置 model,并把 isModelSet 置为 true。没有 model,后面无法构建请求。
java
Request request = buildRequest(target, targetListener, options, callbackExecutor);
这里构建本次请求对象。注意:此时只是构建 Request,还没有启动加载。
java
Request previous = target.getRequest();
从 Target 里取旧请求。对 ViewTarget 来说,旧请求存在 View 的 tag 里。RecyclerView 复用 ImageView 时,这个旧请求非常关键:它让 Glide 能取消上一张图片的加载,避免图片错位。
接着判断:
java
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
如果新请求和旧请求等价,并且不是特殊的 skip memory cache 场景,就复用旧请求。
为什么要判断 skipMemoryCache?看源码:
java
private boolean isSkipMemoryCacheWithCompletePreviousRequest(
BaseRequestOptions<?> options, Request previous) {
return !options.isMemoryCacheable() && previous.isComplete();
}
如果这次请求禁用了内存缓存,而且旧请求已经完成,那么直接 previous.begin() 会用旧请求里已经持有的 resource 重新回调,相当于绕过了"禁用内存缓存"的预期。所以这个场景不能简单复用旧请求。
复用分支:
java
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
这里分三种情况:
- 旧请求正在运行:不做任何事,让它继续跑。
- 旧请求已经完成:调用
begin()重新分发结果,触发 Target/Listener。 - 旧请求失败或暂停:调用
begin()给它重新执行的机会。
不复用时:
java
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
这三行必须连起来理解:
requestManager.clear(target):清理 Target 上的旧请求,取消旧加载、释放旧资源。target.setRequest(request):把新 Request 保存到 Target。requestManager.track(target, request):把请求交给 RequestManager 管理,并在内部触发请求启动。
面试重点:into() 里不是简单 new request.begin()。它先处理旧请求,再把新请求挂到 Target 和 RequestManager 上。这样 Glide 才能在列表复用、页面生命周期变化时取消、暂停、恢复请求。
3. ViewTarget 如何保存和读取旧 Request?
上面用了:
java
Request previous = target.getRequest();
target.setRequest(request);
对 ViewTarget 来说,请求保存在 View tag 中。
源码:
java
@Override
@Nullable
public Request getRequest() {
Object tag = getTag();
Request request = null;
if (tag != null) {
if (tag instanceof Request) {
request = (Request) tag;
} else {
throw new IllegalArgumentException(
"You must not call setTag() on a view Glide is targeting");
}
}
return request;
}
逐段解释:
java
Object tag = getTag();
从 View 的 tag 里取对象。Glide 用 tag 保存当前 View 对应的 Request。
java
if (tag instanceof Request) {
request = (Request) tag;
}
如果 tag 是 Request,就认为它是上一次 into 绑定在这个 View 上的请求。
java
else {
throw new IllegalArgumentException(
"You must not call setTag() on a view Glide is targeting");
}
如果 tag 不是 null,也不是 Request,Glide 直接抛异常。原因是:如果业务自己调用 view.setTag(Object) 或错误使用同一个 tag key 覆盖了 Glide 的 tag,Glide 就找不到旧请求,列表复用时旧请求无法取消,可能出现图片错位、闪烁、资源无法复用。
tag 的读写:
java
private void setTag(@Nullable Object tag) {
isTagUsedAtLeastOnce = true;
view.setTag(tagId, tag);
}
@Nullable
private Object getTag() {
return view.getTag(tagId);
}
这里用的是 setTag(int key, Object tag),不是无 key 的 setTag(Object)。Glide 4 使用自己的默认 tag id,降低和业务 tag 冲突的概率。
面试可答:
Glide 把 Request 存在 ViewTarget 对应 View 的 tag 中。每次 into 同一个 ImageView 时,先从 tag 取出旧 Request,判断能否复用;不能复用就 clear 旧请求,再保存新请求。因此 RecyclerView 复用时,Glide 能知道这个 ImageView 之前加载过什么,并取消旧加载。
4. buildRequest():创建请求协调结构
回到 RequestBuilder.into(Target) 中的:
java
Request request = buildRequest(target, targetListener, options, callbackExecutor);
源码:
java
private Request buildRequest(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
return buildRequestRecursive(
/* requestLock= */ new Object(),
target,
targetListener,
/* parentCoordinator= */ null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions,
callbackExecutor);
}
逐段解释:
java
/* requestLock= */ new Object()
每条请求链创建一个锁对象。主请求、缩略图请求、错误请求如果属于同一条请求链,会共享这个 lock,保证状态变更和回调过程的一致性。
java
/* parentCoordinator= */ null
最外层请求没有父协调器。如果后面存在 thumbnail 或 error,请求会被包进对应 coordinator。
java
transitionOptions
控制资源加载成功后如何过渡展示,比如淡入动画。
java
requestOptions.getPriority()
requestOptions.getOverrideWidth()
requestOptions.getOverrideHeight()
把优先级和 override 尺寸传入构建流程。没有 override 时,后续 SingleRequest.begin() 会去 Target 获取 View 尺寸。
5. buildRequestRecursive():先处理 error 请求
源码:
java
private Request buildRequestRecursive(
Object requestLock,
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
// Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
ErrorRequestCoordinator errorRequestCoordinator = null;
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(requestLock, parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}
Request mainRequest =
buildThumbnailRequestRecursive(
requestLock,
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions,
callbackExecutor);
if (errorRequestCoordinator == null) {
return mainRequest;
}
int errorOverrideWidth = errorBuilder.getOverrideWidth();
int errorOverrideHeight = errorBuilder.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight) && !errorBuilder.isValidOverride()) {
errorOverrideWidth = requestOptions.getOverrideWidth();
errorOverrideHeight = requestOptions.getOverrideHeight();
}
Request errorRequest =
errorBuilder.buildRequestRecursive(
requestLock,
target,
targetListener,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder,
callbackExecutor);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}
逐段解释:
java
ErrorRequestCoordinator errorRequestCoordinator = null;
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(requestLock, parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}
如果用户调用了 error(RequestBuilder) 或传入错误占位请求,Glide 会创建 ErrorRequestCoordinator。它负责协调"主请求失败后,再启动错误请求"。
这里先创建 error coordinator,是为了让后面构建 mainRequest 时知道自己的 parentCoordinator 是谁。
java
Request mainRequest = buildThumbnailRequestRecursive(
requestLock,
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions,
callbackExecutor);
主请求可能还包含 thumbnail,所以继续进入 buildThumbnailRequestRecursive()。
java
if (errorRequestCoordinator == null) {
return mainRequest;
}
如果没有 error 请求,直接返回 mainRequest。
下面处理 error 请求尺寸:
java
int errorOverrideWidth = errorBuilder.getOverrideWidth();
int errorOverrideHeight = errorBuilder.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight) && !errorBuilder.isValidOverride()) {
errorOverrideWidth = requestOptions.getOverrideWidth();
errorOverrideHeight = requestOptions.getOverrideHeight();
}
如果主请求有合法 override 尺寸,而 errorBuilder 自己没有合法 override,那么 error 请求沿用主请求尺寸。这样错误图不会因为尺寸不一致导致额外的问题。
最后递归构建 errorRequest:
java
Request errorRequest =
errorBuilder.buildRequestRecursive(
requestLock,
target,
targetListener,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder,
callbackExecutor);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
最终返回的不是单个 SingleRequest,而可能是一个 ErrorRequestCoordinator。它内部持有 main 和 error 两个请求。
6. buildThumbnailRequestRecursive():处理缩略图请求
源码:
java
private Request buildThumbnailRequestRecursive(
Object requestLock,
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
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.isPrioritySet()
? thumbnailBuilder.getPriority()
: getThumbnailPriority(priority);
int thumbOverrideWidth = thumbnailBuilder.getOverrideWidth();
int thumbOverrideHeight = thumbnailBuilder.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight)
&& !thumbnailBuilder.isValidOverride()) {
thumbOverrideWidth = requestOptions.getOverrideWidth();
thumbOverrideHeight = requestOptions.getOverrideHeight();
}
ThumbnailRequestCoordinator coordinator =
new ThumbnailRequestCoordinator(requestLock, parentCoordinator);
Request fullRequest =
obtainRequest(
requestLock,
target,
targetListener,
requestOptions,
coordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
callbackExecutor);
isThumbnailBuilt = true;
// Recursively generate thumbnail requests.
Request thumbRequest =
thumbnailBuilder.buildRequestRecursive(
requestLock,
target,
targetListener,
coordinator,
thumbTransitionOptions,
thumbPriority,
thumbOverrideWidth,
thumbOverrideHeight,
thumbnailBuilder,
callbackExecutor);
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(requestLock, parentCoordinator);
Request fullRequest =
obtainRequest(
requestLock,
target,
targetListener,
requestOptions,
coordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
callbackExecutor);
BaseRequestOptions<?> thumbnailOptions =
requestOptions.clone().sizeMultiplier(thumbSizeMultiplier);
Request thumbnailRequest =
obtainRequest(
requestLock,
target,
targetListener,
thumbnailOptions,
coordinator,
transitionOptions,
getThumbnailPriority(priority),
overrideWidth,
overrideHeight,
callbackExecutor);
coordinator.setRequests(fullRequest, thumbnailRequest);
return coordinator;
} else {
// Base case: no thumbnail.
return obtainRequest(
requestLock,
target,
targetListener,
requestOptions,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
callbackExecutor);
}
}
这个方法分三条路。
6.1 用户传了 thumbnailBuilder
java
if (thumbnailBuilder != null) {
对应用法类似:
kotlin
Glide.with(view)
.load(fullUrl)
.thumbnail(Glide.with(view).load(thumbUrl))
.into(imageView)
防止递归循环:
java
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()");
}
如果同一个 request 既当主请求又当 thumbnail,会造成递归构建,所以 Glide 直接抛异常,提示使用 clone。
处理缩略图 transition:
java
TransitionOptions<?, ? super TranscodeType> thumbTransitionOptions =
thumbnailBuilder.transitionOptions;
if (thumbnailBuilder.isDefaultTransitionOptionsSet) {
thumbTransitionOptions = transitionOptions;
}
如果 thumbnail 没有自定义 transition,就继承主请求 transition;如果 thumbnail 自己配过,就尊重 thumbnail。
处理缩略图优先级:
java
Priority thumbPriority =
thumbnailBuilder.isPrioritySet()
? thumbnailBuilder.getPriority()
: getThumbnailPriority(priority);
如果 thumbnail 没设置优先级,就比主请求提高一级。getThumbnailPriority() 源码:
java
private Priority getThumbnailPriority(@NonNull Priority current) {
switch (current) {
case LOW:
return Priority.NORMAL;
case NORMAL:
return Priority.HIGH;
case HIGH:
case IMMEDIATE:
return Priority.IMMEDIATE;
default:
throw new IllegalArgumentException("unknown priority: " + getPriority());
}
}
为什么缩略图优先级更高?因为 thumbnail 目标是更快显示一个低质量/小尺寸预览,减少空白等待。
处理缩略图尺寸:
java
int thumbOverrideWidth = thumbnailBuilder.getOverrideWidth();
int thumbOverrideHeight = thumbnailBuilder.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight)
&& !thumbnailBuilder.isValidOverride()) {
thumbOverrideWidth = requestOptions.getOverrideWidth();
thumbOverrideHeight = requestOptions.getOverrideHeight();
}
如果主请求有合法 override,而 thumbnail 自己没有 override,就沿用主请求尺寸。
创建协调器:
java
ThumbnailRequestCoordinator coordinator =
new ThumbnailRequestCoordinator(requestLock, parentCoordinator);
这个 coordinator 管理 fullRequest 和 thumbRequest 的关系。比如缩略图先展示,主图成功后替换缩略图。
创建 fullRequest:
java
Request fullRequest =
obtainRequest(
requestLock,
target,
targetListener,
requestOptions,
coordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
callbackExecutor);
注意 fullRequest 的 parent/requestCoordinator 是 coordinator。
递归创建 thumbRequest:
java
isThumbnailBuilt = true;
Request thumbRequest =
thumbnailBuilder.buildRequestRecursive(
requestLock,
target,
targetListener,
coordinator,
thumbTransitionOptions,
thumbPriority,
thumbOverrideWidth,
thumbOverrideHeight,
thumbnailBuilder,
callbackExecutor);
isThumbnailBuilt = false;
这里递归是因为 thumbnailBuilder 自己也可能有 error 或 thumbnail。
最后:
java
coordinator.setRequests(fullRequest, thumbRequest);
return coordinator;
返回 coordinator,而不是某个单独请求。
6.2 用户用了 thumbnail(sizeMultiplier)
java
} else if (thumbSizeMultiplier != null) {
对应用法:
kotlin
Glide.with(view)
.load(url)
.thumbnail(0.25f)
.into(imageView)
这里没有单独的 thumbnailBuilder,而是用同一个 model 创建一个缩小尺寸的请求。
java
BaseRequestOptions<?> thumbnailOptions =
requestOptions.clone().sizeMultiplier(thumbSizeMultiplier);
克隆主请求 options,然后设置 sizeMultiplier。这样 thumbnail 和 fullRequest 的 model 相同,但请求尺寸会按比例缩小。
java
Request thumbnailRequest =
obtainRequest(
requestLock,
target,
targetListener,
thumbnailOptions,
coordinator,
transitionOptions,
getThumbnailPriority(priority),
overrideWidth,
overrideHeight,
callbackExecutor);
创建 thumbnailRequest。
java
coordinator.setRequests(fullRequest, thumbnailRequest);
return coordinator;
同样返回 ThumbnailRequestCoordinator。
6.3 没有 thumbnail
java
} else {
return obtainRequest(
requestLock,
target,
targetListener,
requestOptions,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
callbackExecutor);
}
这是最简单的主链路:没有 error、没有 thumbnail 时,最终会直接 obtainRequest() 创建一个 SingleRequest。
7. obtainRequest():真正创建 SingleRequest
源码:
java
private Request obtainRequest(
Object requestLock,
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> requestOptions,
RequestCoordinator requestCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
Executor callbackExecutor) {
return SingleRequest.obtain(
context,
glideContext,
requestLock,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListeners,
requestCoordinator,
glideContext.getEngine(),
transitionOptions.getTransitionFactory(),
callbackExecutor);
}
逐个参数解释:
java
context, glideContext
context 是 Android Context;glideContext 是 Glide 内部上下文,里面有 Registry、Engine、默认配置等。
java
requestLock
同一请求链共享的锁。
java
model
load(url) 传入的对象。它不一定是 String,也可以是 Uri、File、资源 id、自定义 model。
java
transcodeClass
目标转码类型,比如 Drawable.class、Bitmap.class、File.class。
java
requestOptions
本次请求选项,包括缓存策略、transform、override 尺寸、placeholder、error 图、priority 等。
java
overrideWidth, overrideHeight
如果用户调用了 override(w, h),这里就是指定尺寸;否则后续要从 Target 获取尺寸。
java
target
最终接收结果的 Target。对 ImageView 来说是 ViewTarget 的子类。
java
targetListener, requestListeners
单个 Target listener 和 builder 上注册的 listeners。
java
requestCoordinator
可能是 error coordinator、thumbnail coordinator,也可能是 parentCoordinator/null,用于控制谁能设置图片、谁能通知状态。
java
glideContext.getEngine()
Engine 是后续真正查缓存、创建 DecodeJob 的核心对象。本篇先不展开,下一篇完整讲。
java
transitionOptions.getTransitionFactory()
加载成功后如何过渡到目标 View。
java
callbackExecutor
回调执行器。into(ImageView) 传的是主线程执行器。
8. SingleRequest 构造完成后的初始状态
SingleRequest 构造相关源码片段:
java
this.targetListener = targetListener;
this.requestListeners = requestListeners;
this.requestCoordinator = requestCoordinator;
this.engine = engine;
this.animationFactory = animationFactory;
this.callbackExecutor = callbackExecutor;
status = Status.PENDING;
if (requestOrigin == null && glideContext.getExperiments().isEnabled(LogRequestOrigins.class)) {
requestOrigin = new RuntimeException("Glide request origin trace");
}
逐段解释:
java
this.requestCoordinator = requestCoordinator;
如果有 thumbnail/error,这里保存 coordinator;后面资源加载成功时,SingleRequest 会询问 coordinator 当前请求能不能设置资源、能不能通知状态。
java
this.engine = engine;
后续 onSizeReady() 会调用 engine.load()。
java
this.callbackExecutor = callbackExecutor;
资源成功/失败后,回调会通过这个 executor 执行。ImageView 场景下是主线程。
java
status = Status.PENDING;
刚创建出来的请求是 PENDING,还没开始。
9. requestManager.track(target, request) 才会启动请求
在 into(Target) 最后:
java
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
track() 的内部会把 Target 加入 TargetTracker,把 Request 交给 RequestTracker。如果当前 RequestManager 没有暂停,请求会被启动,也就是进入 request.begin()。
这里先记住面试结论:
buildRequest()只是创建请求对象,真正启动由RequestManager.track()管理。这样 Glide 才能统一处理 Activity/Fragment 生命周期里的 pause/resume/clear。
下一篇讲生命周期时,需要完整展开 RequestManager.track()、RequestTracker.runRequest()、LifecycleListener。
10. SingleRequest.begin():请求开始,但仍然不是直接下载
源码:
java
@Override
public void begin() {
synchronized (requestLock) {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
// If we're restarted after we're complete (usually via something like a notifyDataSetChanged
// that starts an identical request into the same Target or View), we can simply use the
// resource and size we retrieved the last time around and skip obtaining a new size, starting
// a new load etc. This does mean that users who want to restart a load because they expect
// that the view size has changed will need to explicitly clear the View or Target before
// starting the new load.
if (status == Status.COMPLETE) {
onResourceReady(
resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return;
}
// Restarts for requests that are neither complete nor running can be treated as new requests
// and can run again from the beginning.
experimentalNotifyRequestStarted(model);
cookie = GlideTrace.beginSectionAsync(TAG);
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
}
逐段解释:
java
synchronized (requestLock) {
整个 begin 过程受同一个 requestLock 保护。主请求、thumbnail、error 可能共享同一把锁,避免状态竞争。
java
assertNotCallingCallbacks();
防止在 Glide 的回调过程中再次 start/clear 请求。相关源码:
java
private void assertNotCallingCallbacks() {
if (isCallingCallbacks) {
throw new IllegalStateException(
"You can't start or clear loads in RequestListener or"
+ " Target callbacks. If you're trying to start a fallback request when a load fails,"
+ " use RequestBuilder#error(RequestBuilder). Otherwise consider posting your into()"
+ " or clear() calls to the main thread using a Handler instead.");
}
}
为什么限制?因为在 RequestListener 或 Target 回调中同步启动/清理请求,容易造成请求状态重入,甚至资源释放时机混乱。Glide 建议错误兜底用 error(RequestBuilder),或者 post 到主线程下一轮执行。
java
stateVerifier.throwIfRecycled();
确认这个 SingleRequest 没有被回收复用。Glide 内部有对象池,如果对象已 recycled 还继续用,会造成难查的状态错误。
java
startTime = LogTime.getLogTime();
记录开始时间,用于 verbose 日志耗时统计。
10.1 model 为 null 的分支
java
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
如果 load(null),Glide 不会进入 Engine,也不会尝试网络请求。
如果 override 尺寸有效,先把 width/height 记录下来。然后根据是否设置 fallback drawable 决定日志等级:
- 没设置 fallback:
WARN,因为 null model 可能是异常。 - 设置了 fallback:
DEBUG,因为用户可能预期 model 偶尔为空。
最后走 onLoadFailed()。
10.2 正在运行的请求不能重复 begin
java
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
同一个 SingleRequest 如果已经 RUNNING,再 begin 会抛异常。注意这和前面 into(Target) 的复用逻辑不冲突:如果旧请求正在运行,into(Target) 根本不会再次 begin。
10.3 已完成请求再次 begin,直接回调已有资源
java
if (status == Status.COMPLETE) {
onResourceReady(
resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return;
}
如果请求已经完成,再次 begin 不重新拿尺寸、不重新走 Engine,而是把当前持有的 resource 重新回调给 Target。
源码注释给的典型场景是 notifyDataSetChanged 后,同一个 Target/View 发起等价请求。Glide 可以跳过 placeholder、尺寸获取和加载流程。
10.4 通知实验性 listener
java
experimentalNotifyRequestStarted(model);
源码:
java
private void experimentalNotifyRequestStarted(Object model) {
if (requestListeners == null) {
return;
}
for (RequestListener<?> requestListener : requestListeners) {
if (requestListener instanceof ExperimentalRequestListener) {
((ExperimentalRequestListener<?>) requestListener).onRequestStarted(model);
}
}
}
如果没有 listeners,直接返回。如果 listener 实现了 ExperimentalRequestListener,就通知请求开始。
10.5 状态改为等待尺寸
java
cookie = GlideTrace.beginSectionAsync(TAG);
status = Status.WAITING_FOR_SIZE;
Glide 开启一段异步 trace,并把请求状态改成 WAITING_FOR_SIZE。
重点:到这里依然没有下载图片。Glide 要先知道目标尺寸。
10.6 有 override 尺寸就直接 onSizeReady
java
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
如果用户调用了:
kotlin
Glide.with(view)
.load(url)
.override(200, 200)
.into(imageView)
那么 overrideWidth/overrideHeight 有效,直接进入 onSizeReady()。
如果没有 override,就调用 target.getSize(this)。这里的 this 是 SingleRequest,它实现了 SizeReadyCallback,等 Target 算出尺寸后会回调 SingleRequest.onSizeReady(width, height)。
10.7 展示 placeholder
java
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
这里有两个条件:
- 请求状态仍然是 RUNNING 或 WAITING_FOR_SIZE。
requestCoordinator允许通知状态变化。
为什么可能是 RUNNING?因为如果 override 尺寸有效,onSizeReady() 会被同步调用,里面会把状态改为 RUNNING。
为什么需要 canNotifyStatusChanged()?如果有 thumbnail/error coordinator,不是所有子请求都能随便改 UI 状态,必须由 coordinator 判断。
11. target.getSize(this):ViewTarget 如何计算尺寸
SingleRequest.begin() 里如果没有 override,会调用:
java
target.getSize(this);
对 ViewTarget 来说,核心逻辑在 SizeDeterminer.getSize()。
源码:
java
void getSize(@NonNull SizeReadyCallback cb) {
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
return;
}
// We want to notify callbacks in the order they were added and we only expect one or two
// callbacks to be added a time, so a List is a reasonable choice.
if (!cbs.contains(cb)) {
cbs.add(cb);
}
if (layoutListener == null) {
ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
}
}
逐段解释:
java
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
先尝试立刻获取目标宽高。
java
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
return;
}
如果当前尺寸有效,立即回调 SingleRequest.onSizeReady()。这说明不一定每次都要等下一次布局。
java
if (!cbs.contains(cb)) {
cbs.add(cb);
}
如果当前尺寸无效,把 callback 加入列表。这里是 List,因为通常只有一两个 callback,比如主请求和缩略图请求。
java
if (layoutListener == null) {
ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
}
注册 OnPreDrawListener,等 View 下一次绘制前再检查尺寸。
12. ViewTarget 尺寸计算规则
宽高入口:
java
private int getTargetHeight() {
int verticalPadding = view.getPaddingTop() + view.getPaddingBottom();
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.height : PENDING_SIZE;
return getTargetDimen(view.getHeight(), layoutParamSize, verticalPadding);
}
private int getTargetWidth() {
int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight();
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.width : PENDING_SIZE;
return getTargetDimen(view.getWidth(), layoutParamSize, horizontalPadding);
}
逐段解释:
java
int verticalPadding = view.getPaddingTop() + view.getPaddingBottom();
int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight();
目标图片可用区域要扣掉 padding。
java
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.height : PENDING_SIZE;
优先考虑布局参数里的 width/height。如果没有 LayoutParams,就认为尺寸暂不可用。
真正计算在 getTargetDimen():
java
private int getTargetDimen(int viewSize, int paramSize, int paddingSize) {
// We consider the View state as valid if the View has non-null layout params and a non-zero
// layout params width and height. This is imperfect. We're making an assumption that View
// parents will obey their child's layout parameters, which isn't always the case.
int adjustedParamSize = paramSize - paddingSize;
if (adjustedParamSize > 0) {
return adjustedParamSize;
}
// Since we always prefer layout parameters with fixed sizes, even if waitForLayout is true,
// we might as well ignore it and just return the layout parameters above if we have them.
// Otherwise we should wait for a layout pass before checking the View's dimensions.
if (waitForLayout && view.isLayoutRequested()) {
return PENDING_SIZE;
}
// We also consider the View state valid if the View has a non-zero width and height. This
// means that the View has gone through at least one layout pass. It does not mean the Views
// width and height are from the current layout pass. For example, if a View is re-used in
// RecyclerView or ListView, this width/height may be from an old position. In some cases
// the dimensions of the View at the old position may be different than the dimensions of the
// View in the new position because the LayoutManager/ViewParent can arbitrarily decide to
// change them. Nevertheless, in most cases this should be a reasonable choice.
int adjustedViewSize = viewSize - paddingSize;
if (adjustedViewSize > 0) {
return adjustedViewSize;
}
// Finally we consider the view valid if the layout parameter size is set to wrap_content.
// It's difficult for Glide to figure out what to do here. Although Target.SIZE_ORIGINAL is a
// coherent choice, it's extremely dangerous because original images may be much too large to
// fit in memory or so large that only a couple can fit in memory, causing OOMs. If users want
// the original image, they can always use .override(Target.SIZE_ORIGINAL). Since wrap_content
// may never resolve to a real size unless we load something, we aim for a square whose length
// is the largest screen size. That way we're loading something and that something has some
// hope of being downsampled to a size that the device can support. We also log a warning that
// tries to explain what Glide is doing and why some alternatives are preferable.
// Since WRAP_CONTENT is sometimes used as a default layout parameter, we always wait for
// layout to complete before using this fallback parameter (ConstraintLayout among others).
if (!view.isLayoutRequested() && paramSize == LayoutParams.WRAP_CONTENT) {
if (Log.isLoggable(TAG, Log.INFO)) {
Log.i(
TAG,
"Glide treats LayoutParams.WRAP_CONTENT as a request for an image the size of this"
+ " device's screen dimensions. If you want to load the original image and are"
+ " ok with the corresponding memory cost and OOMs (depending on the input size),"
+ " use override(Target.SIZE_ORIGINAL). Otherwise, use LayoutParams.MATCH_PARENT,"
+ " set layout_width and layout_height to fixed dimension, or use .override()"
+ " with fixed dimensions.");
}
return getMaxDisplayLength(view.getContext());
}
// If the layout parameters are < padding, the view size is < padding, or the layout
// parameters are set to match_parent or wrap_content and no layout has occurred, we should
// wait for layout and repeat.
return PENDING_SIZE;
}
按优先级解释:
- 先看 LayoutParams 固定尺寸,扣掉 padding 后大于 0 就用它。
- 如果设置了
waitForLayout且 View 正在请求布局,就返回 pending,等待布局完成。 - 再看 View 当前实际宽高,扣掉 padding 后大于 0 就用它。
- 如果布局已经完成但 LayoutParams 是
wrap_content,Glide 使用屏幕最大边作为兜底尺寸,而不是原图尺寸。 - 其他情况返回
PENDING_SIZE,继续等待布局。
为什么 wrap_content 不直接用原图?源码注释说得很明确:原图可能非常大,容易 OOM。用户真想加载原图,应显式使用 override(Target.SIZE_ORIGINAL)。
尺寸有效判断:
java
private boolean isDimensionValid(int size) {
return size > 0 || size == SIZE_ORIGINAL;
}
所以合法尺寸只有两种:大于 0,或者明确要求 SIZE_ORIGINAL。
13. 下一步连接点:onSizeReady()
本篇到这里为止,链路已经从 into(ImageView) 完整走到了请求等待/获取尺寸。
下一篇的起点就是:
java
SingleRequest.onSizeReady(width, height)
从这里会进入:
text
SingleRequest.onSizeReady()
-> Engine.load()
-> EngineKey
-> ActiveResources
-> MemoryCache
-> Jobs
-> EngineJob
-> DecodeJob
14. 总结
问:Glide.with().load().into(imageView) 之后发生了什么?
回答:
into(ImageView)首先要求在主线程执行,然后根据 ImageView 的 ScaleType,在用户没有显式设置 transformation 的情况下,为请求补充可选的 centerCrop、fitCenter 等变换。接着 Glide 会把 ImageView 包装成 ViewTarget,并进入into(Target)。在
into(Target)中,Glide 会先检查是否调用过load(),然后构建 Request。构建过程中会处理 error 请求和 thumbnail 请求,所以最终得到的 Request 不一定是单个 SingleRequest,也可能是 ErrorRequestCoordinator 或 ThumbnailRequestCoordinator。没有 error/thumbnail 时,最终通过SingleRequest.obtain()创建 SingleRequest。随后 Glide 会从 ViewTarget 的 tag 中取出旧 Request。如果新旧请求等价,并且不是 skipMemoryCache 的特殊场景,就复用旧请求;否则先 clear 旧请求,再把新 Request 保存到 View tag,并交给 RequestManager track。RequestManager 负责启动请求以及生命周期管理。
请求启动后进入
SingleRequest.begin()。begin 会先处理 null model、重复运行、已完成复用等状态,然后把状态改为WAITING_FOR_SIZE。如果有 override 尺寸就直接进入onSizeReady();否则通过 Target 获取 View 尺寸。ViewTarget 会优先用 LayoutParams 固定尺寸,其次用 View 当前尺寸,wrap_content 会用屏幕最大边兜底,尺寸还不可用时注册 OnPreDrawListener 等待下一次绘制。