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); } }
ここまでで、 Target
と Request
という2つのクラスが作成されました。また、Request#begin
でリクエストが始まるところまでをみていきました。
次は、リクエストの生成処理(buildRequest
)についてみていきたいと思います。
具体的なRequestインスタンスの生成
Request
はinterfaceのため、具体的な実装は各具象クラスに記述されています。そのため、先ほど無視したRequest
インスタンスの生成処理に戻りたいと思います(RequestBuilder#buildRequest
)。
glide/RequestBuilder.java at v4.11.0 · bumptech/glide
buildRequest
には buildRequestRecursive
を呼び出す処理があり、何やら再帰的にRequest
を生成していそうです。
buildRequestRecursive
では、thumbnail
やerror
の設定を行ったかどうかによって処理が分岐されています。
この時に設定したエラーやサムネイルのRequestBuilder
は、load
で返却されるRequestBuilder
の中で保持されるので再帰的な構造になっています。
thumbnail
や error
の設定を行うコード例
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/Height
やLayoutParams
などからサイズを求めることが可能な場合は計算を行った結果で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の画像リクエストが始まるところから表示するまでの流れをみてきました。
簡単にまとめると、以下のような流れとなっていました。
RequestBuilder
からRequest
とTarget
を生成- 生成した
Request
をRequestManger
で管理/実行する Target
でサイズを計算するEngine
が実際に画像を取得する(リモートやキャッシュから)- 取得した画像を
Target
が表示する
途中のEngine
の処理の詳細については触れませんでしたが、それでもGlideが多くのことを行っていることが分かりました。
Glideの画像取得処理の肝であるEngine
に関しては、また別でみていきたいと思います!