Glide 源码学习系列

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());

这里发生了四件事:

  1. ImageView 被包装成 ViewTarget<ImageView, TranscodeType>
  2. targetListener 为 null,说明没有额外给 Target 绑定单独 listener。
  3. 传入刚才处理过 ScaleType 的 requestOptions
  4. 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;

这里分三种情况:

  1. 旧请求正在运行:不做任何事,让它继续跑。
  2. 旧请求已经完成:调用 begin() 重新分发结果,触发 Target/Listener。
  3. 旧请求失败或暂停:调用 begin() 给它重新执行的机会。

不复用时:

java 复制代码
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);

这三行必须连起来理解:

  1. requestManager.clear(target):清理 Target 上的旧请求,取消旧加载、释放旧资源。
  2. target.setRequest(request):把新 Request 保存到 Target。
  3. 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.classBitmap.classFile.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.");
  }
}

为什么限制?因为在 RequestListenerTarget 回调中同步启动/清理请求,容易造成请求状态重入,甚至资源释放时机混乱。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)。这里的 thisSingleRequest,它实现了 SizeReadyCallback,等 Target 算出尺寸后会回调 SingleRequest.onSizeReady(width, height)

10.7 展示 placeholder

java 复制代码
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
    && canNotifyStatusChanged()) {
  target.onLoadStarted(getPlaceholderDrawable());
}

这里有两个条件:

  1. 请求状态仍然是 RUNNING 或 WAITING_FOR_SIZE。
  2. 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;
}

按优先级解释:

  1. 先看 LayoutParams 固定尺寸,扣掉 padding 后大于 0 就用它。
  2. 如果设置了 waitForLayout 且 View 正在请求布局,就返回 pending,等待布局完成。
  3. 再看 View 当前实际宽高,扣掉 padding 后大于 0 就用它。
  4. 如果布局已经完成但 LayoutParams 是 wrap_content,Glide 使用屏幕最大边作为兜底尺寸,而不是原图尺寸。
  5. 其他情况返回 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 等待下一次绘制。

相关推荐
谷哥的小弟1 天前
图文详解Spring Boot整合MyBatisPlus(附源码)
mybatis·源码·springboot·mybatis-plus·整合
万岳科技程序员小金3 天前
真人数字人系统源码开发指南:一套平台如何支撑多端应用(APP/小程序)
源码·软件开发·ai数字人小程序·ai数字人系统源码·ai真人数字人app开发·数字人平台搭建
工业互联网专业4 天前
国潮男装微博评论数据分析系统的设计与实现 _flask+spider
python·flask·毕业设计·源码·课程设计·spider
大叔_爱编程4 天前
基于职业能力的知识图谱的学习路径推荐系统_django
django·毕业设计·源码·知识图谱·课程设计
元思未来6 天前
Hermes Agent 源码探秘 (5):System Prompt 组装 — Agent 的"灵魂"
源码·agent
淘源码d7 天前
产科系统源码,数字产科源码,Java(后端) + Vue + ElementUI(前端) + MySQL(数据库),确保系统稳定性与扩展性。
java·源码·数字产科·产科系统·智能化孕产服务·高危五色预警·智慧产科
爱笑的源码基地9 天前
小微企业ERP源码,采用SpringBoot+Vue+ElementUI+UniAPP技术架构,支持二次开发及商用授权
java·源码·二次开发·erp·源代码·mrp生产计划
不简说11 天前
前端可视化打印设计器sv-print,一口气更新了30版
前端·源码·产品
坐吃山猪11 天前
【Nanobot】README04_LEVEL2 提供商系统设计
python·源码·agent·nanobot