Glideの画像リクエストが始まるところから表示するまでの流れをおいかける

今回みる処理

以下のようにload(String).into(ImageView) を実行してから、画像が表示されるまでの処理の流れをみていきます。

Glide
    .with(imageView)
    .load(imageUrl)
    .into(imageView);

intoでリクエストを開始するまで

into(ImageView) の中では、ロード処理の開始や成功などのイベント通知を受け取る Target を作成します。 今回の場合は、ロード成功時にViewに画像をセットする ImageViewTarget が作成されます。

return into(
    glideContext.buildImageViewTarget(view, transcodeClass),
    /*targetListener=*/ null,
    requestOptions,
    Executors.mainThreadExecutor());

最終的に以下の into メソッドの呼び出しが行われ、Request インスタンスの生成が行われています。名前的にこのクラスが画像読み込みに大きく関係してきそうです。どのようなインスタンスが作成されるかについては一旦置いておき、全体の流れをみたいと思います。
ここで登場する requestManager は、ActivityやFragmentなどのライフサイクルに合わせてリクエスト処理の開始/停止を管理するクラスです。
requestManager の生成については、Glide.withに渡す引数による処理の違いについて - scache’s blogで書いているので興味がある方はそちらを参考にしてみてください。

private <Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    BaseRequestOptions<?> options,
    Executor callbackExecutor) {
    ...
    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    Request previous = target.getRequest();
    ...  // TargetにセットされているRequestが今回作ったリクエストと同じだったら、セットされているRequestをそのまま使い、targetを返す

    requestManager.clear(target);
    target.setRequest(request);
    requestManager.track(target, request);
    return target;
}

最後の方にある requestManager.track(target, request); の中では runRequest(request) が実行されていて、ここからリクエストが開始しそうなことが分かります。

synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
}

実際に、 RequestTracker#runRequest の中で Request#begin が呼び出されてリクエストが開始されます。(RequestTracker がpausedの場合はresumeになった時に begin が呼び出されます)

public void runRequest(@NonNull Request request) {
    requests.add(request);
    if (!isPaused) {
        request.begin();
    } else {
        request.clear();
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Paused, delaying request");
        }
        pendingRequests.add(request);
    }
}

ここまでで、 TargetRequest という2つのクラスが作成されました。また、Request#beginでリクエストが始まるところまでをみていきました。

次は、リクエストの生成処理(buildRequest)についてみていきたいと思います。

具体的なRequestインスタンスの生成

Request はinterfaceのため、具体的な実装は各具象クラスに記述されています。そのため、先ほど無視したRequestインスタンスの生成処理に戻りたいと思います(RequestBuilder#buildRequest)。
glide/RequestBuilder.java at v4.11.0 · bumptech/glide

buildRequest には buildRequestRecursive を呼び出す処理があり、何やら再帰的にRequestを生成していそうです。

buildRequestRecursive では、thumbnailerrorの設定を行ったかどうかによって処理が分岐されています。 この時に設定したエラーやサムネイルのRequestBuilderは、load で返却されるRequestBuilderの中で保持されるので再帰的な構造になっています。

thumbnailerror の設定を行うコード例

Glide
    .with(imageView)
    .load(imageUrl)
    .thumbnail(thumbRequestBuilder)
    .error(errorRequestBuilder)
    .into(imageView);

まず、buildRequestRecursive ではerrorが設定されているかどうかで以下のように処理が分岐します。

errorBuilderが
    ない -> buildThumbnailRequestRecursiveで生成したRequestを返す
    ある -> 下の2つのRequestを設定したErrorRequestCoordinatorを返す
            - buildThumbnailRequestRecursiveと
            - errorで指定したRequestBuilderのbuildRequestRecursive

どちらのパターンでも呼び出される buildThumbnailRequestRecursive が行っていることはbuildRequestRecursiveと似ており、thumbが設定されている場合は、loadで指定した画像を読み込むRequestとサムネイルを読み込むたむRequestの2つが設定されたThumbnailRequestCoordinatorを返します。

一度整理すると、3つのリクエストの概念があります。 - fullRequest : loadで指定した画像のリクエスト - thumbRequest: thumbで指定した画像のリクエスト - errorRequest: errorで指定した画像のリクエス

再帰的な構造になっているため複雑になってきましたが、1番シンプルなエラーやサムネイルを設定しなかった時の処理をみていきましょう。
この時は、fullRequestのみが作成されます。具体的にインスタンスを生成する処理はSingleRequest.obtainとなっています(buildRequestでこのSingleRequestが返却される)。
glide/RequestBuilder.java at v4.11.0 · bumptech/glide

エラーやサムネイルの設定がある時についてはスルーしていますが、Requestの具体的な型がSingleRequestになることが分かりました。(詳しくは説明を行いませんが、再帰的に処理が行われるためエラーやサムネイルのCoordinatorに設定するリクエストも辿っていくと最終的にSingleRequestになります)

SingleRequestを開始する

Request を生成する処理が長かったですが、これでようやくリクエストを開始する処理をみていくことができます。

リクエストは Request#begin で始まるので Single#begin の処理をみていきます。 ログやコメントなどを除いた begin の処理は以下のようになっています。

@Override
public void begin() {
  synchronized (requestLock) {
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      int logLevel = ...
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }

    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }

    if (status == Status.COMPLETE) {
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }

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

ステータスは、インスタンス生成時はPENDINGになっており、overrideWidthなどを設定するかによって多少かわりますが PENDING -> WAITING_FOR_SIZE -> RUNNING -> COMPLETE のように遷移をします。

model(画像URLなど)が設定されていなかったり、すでにリクエストが動いている時はエラーになります。また、すでに画像読み込み済みの場合は、onResourceReadyが呼び出されます。

WAITING_FOR_SIZE -> RUNNING

まずはWAITING_FOR_SIZEからRUNNINGになるまでをみていきます。

status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
    onSizeReady(overrideWidth, overrideHeight);
} else {
    target.getSize(this);
}

有効なoverrideWidth, overrideHeightが設定されている場合はすぐにonSizeReadyが呼び出されるのでこれ以上説明することはありません。
設定されていない場合は、Target#getSize(SizeReadyCallback)の呼び出しが行われてTargetにサイズを要求しています。サイズが確定したらコールバックが呼ばれ、その時にonSizeReadyの呼び出しが行われます。

ImageViewに画像を設定する際は ImageViewTarget が使われるため、ImageViewTarget#getSize の処理をみたいと思います(実際の処理は継承元のViewTargetにあります)。

getSize ではViewのgetWidth/HeightLayoutParamsなどからサイズを求めることが可能な場合は計算を行った結果でonSizeReadyの呼び出しを行います。
しかし、Viewがレイアウトリクエスト中(View#isLayoutRequested)など、サイズを求めることができなかった場合は、OnPreDrawListenerをセットしてサイズが確定するのを待ち、onPreDraw内でonSizeReadyの呼び出しを行います。

ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);

このようにViewのサイズ計算を行うことでサイズを決定し、RUNNING状態に遷移します。

RUNNING -> COMPLETE

サイズを決定してonSizeReadyの呼び出しを行うところまでをみたため、ここからはonSizeReadyで行っている処理についてみていきます。
glide/SingleRequest.java at v4.11.0 · bumptech/glide

onSizeReady は状態のチェックを除けば Engine#load を呼び出しているのみになります。 Engine#load は引数がかなり多く(19個、読むのをやめたくなりますが、少しずつみていきましょう 😓
glide/Engine.java at v4.11.0 · bumptech/glide

はじめにメモリキャッシュにデータが存在するかチェックを行い、存在する場合はResourceCallback#onResourceReady(Resource<?>, DataSource)が実行されます。最終的に画像読み込み後はonResourceReadyの呼び出しが行われそうということが分かりました。

少しずつみていくと言いましたがEngine#loadの処理はとても長くなりそうなので、別の記事で書くことにしてこの記事の本来の目的である画像が表示されるまでの流れに戻るためにonResourceReadyの呼び出しで何が起こるかをみていきたいと思います。

Engine に渡したResourceCallbackインスタンスSingleRequestインスタンスのため、SingleRequest#onResourceReadyの処理にいきます。

SingleRequest には2つのonResourceReadyメソッドがあり、①がEngineから呼び出されます。①ではResource#getで取得できるインスタンスの型が、RequestBuilderの型パラメータ(今回はDrawable)と一致するかをチェックして、一致する場合は②が呼び出されます(もし一致しなかった場合はエラーになります)。

① onResourceReady(Resource<?>, DataSource)
② onResourceReady(Resource<R>, R, DataSource)

ここで呼び出されたメソッドでステータスがCOMPLETEにかわります。また、Target#onResourceReadyが呼び出されます。

SingleRequest#onResourceReady(Resource<R>, R, DataSource)
glide/SingleRequest.java at v4.11.0 · bumptech/glide

ImageViewTargetに画像読み込み完了のコールバックが呼ばれてから

ここまで長かったですが、この時点で画像は読み込まれているので後はImageViewに画像をセットするだけとなりました。

onResourceReadyとその中で呼び出されるsetResourceInternalは以下のようになっていて、最後はsetResourceが実行されます。

public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
        setResourceInternal(resource);
    } else {
        maybeUpdateAnimatable(resource);
    }
}

private void setResourceInternal(@Nullable Z resource) {
  setResource(resource);
  maybeUpdateAnimatable(resource);
}

今回のintoで実際に生成されるTargetインスタンスの型はDrawableImageViewTargetになり、そのsetResourceでようやくImageViewに画像がセットされます。

protected void setResource(@Nullable Drawable resource) {
  view.setImageDrawable(resource);
}

まとめ

今回はGlideの画像リクエストが始まるところから表示するまでの流れをみてきました。
簡単にまとめると、以下のような流れとなっていました。

  1. RequestBuilderからRequestTargetを生成
  2. 生成したRequestRequestMangerで管理/実行する
  3. Targetでサイズを計算する
  4. Engineが実際に画像を取得する(リモートやキャッシュから)
  5. 取得した画像をTargetが表示する

途中のEngineの処理の詳細については触れませんでしたが、それでもGlideが多くのことを行っていることが分かりました。
Glideの画像取得処理の肝であるEngineに関しては、また別でみていきたいと思います!