Glide 系列-2:主流程原始碼分析(4.8.0)
Glide 是 Android 端比較常用的圖片載入框架,這裡我們就不再介紹它的基礎的使用方式。你可以通過檢視其官方文件學習其基礎使用。這裡,我們給出一個 Glide 的最基本的使用示例,並以此來研究這個整個過程發生了什麼:
Glide.with(fragment).load(myUrl).into(imageView);
上面的程式碼雖然簡單,但是整個執行過程涉及許多類,其流程也比較複雜。為了更清楚地說明這整個過程,我們將 Glide 的圖片載入按照呼叫的時間關係分成了下面幾個部分:
with()
方法的執行過程load()
方法的執行過程into()
- 階段1:開啟
DecodeJob
的過程 - 階段2:開啟網路流的過程
- 階段3:將輸入流轉換為
Drawable
的過程 - 階段4:將
Drawable
展示到ImageView
的過程
- 階段1:開啟
即按照上面的示例程式碼,先分成 with()
、load()
和 into()
三個過程,而 into()
過程又被細化成四個階段。
下面我們就按照上面劃分的過程來分別介紹一下各個過程中都做了哪些操作。
1、with() 方法的執行過程
1.1 例項化單例的 Glide 的過程
當呼叫了 Glide 的 with()
RequestManager
例項。with()
有多個過載方法,我們可以使用 Activity
或者 Fragment
等來獲取 Glide
例項。它們最終都會呼叫下面這個方法來完成最終的操作:
public static RequestManager with(Context context) {
return getRetriever(context).get(context);
}
在 getRetriever()
方法內部我們會先使用 Glide
的 get()
方法獲取一個單例的 Glide 例項,然後從該 Glide 例項中得到一個 RequestManagerRetriever
private static RequestManagerRetriever getRetriever(Context context) {
return Glide.get(context).getRequestManagerRetriever();
}
這裡呼叫了 Glide 的 get()
方法,它最終會呼叫 initializeGlide()
方法例項化一個單例的 Glide
例項。在之前的文中我們已經介紹了這個方法。它主要用來從註解和 Manifest 中獲取 GlideModule,並根據各 GlideModule 中的方法對 Glide 進行自定義:
《Glide 系列-1:預熱、Glide 的常用配置方式及其原理》
下面的方法中需要傳入一個 GlideBuilder
例項。很明顯這是一種構建者模式的應用,我們可以使用它的方法來實現對 Glide 的個性化配置:
private static void initializeGlide(Context context, GlideBuilder builder) {
// ... 各種操作,略
// 賦值給靜態的單例例項
Glide.glide = glide;
}
最終 Glide 例項由 GlideBuilder
的 build()
方法構建完畢。它會直接呼叫 Glide 的構造方法來完成 Glide 的建立。在該構造方法中會將各種型別的圖片資源及其對應的載入類的對映關係註冊到 Glide 中,你可以閱讀原始碼瞭解這部分內容。
1.2 Glide 的生命週期管理
在 with()
方法的執行過程還有一個重要的地方是 Glide 的生命週期管理。因為當我們正在進行圖片載入的時候,Fragment 或者 Activity 的生命週期可能已經結束了,所以,我們需要對 Glide 的生命週期進行管理。
Glide 對這部分內容的處理也非常巧妙,它使用沒有 UI 的 Fragment 來管理 Glide 的生命週期。這也是一種非常常用的生命週期管理方式,比如 RxPermission
等框架都使用了這種方式。你可以通過下面的示例來了解它的作用原理:
示例程式碼:使用 Fragment 管理 onActivityResult()
在 with()
方法中,當我們呼叫了 RequestManagerRetriever
的 get()
方法之後,會根據 Context 的型別呼叫 get()
的各個過載方法。
public RequestManager get(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
我們以 Activity 為例。如下面的方法所示,噹噹前位於後臺執行緒的時候,會使用 Application 的 Context 獲取 RequestManager
,否則會使用無 UI 的 Fragment 進行管理:
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
然後就呼叫到了 fragmentGet()
方法。這裡我們從 RequestManagerFragment
中通過 getGlideLifecycle()
獲取到了 Lifecycle
物件。Lifecycle
物件提供了一系列的、針對 Fragment 生命週期的方法。它們將會在 Fragment 的各個生命週期方法中被回撥。
private RequestManager fragmentGet(Context context, FragmentManager fm,
Fragment parentHint, boolean isParentVisible) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
然後,我們將該 Lifecycle
傳入到 RequestManager
中,以 RequestManager
中的兩個方法為例,RequestManager
會對 Lifecycle
進行監聽,從而達到了對 Fragment 的生命週期進行監聽的目的:
public void onStart() {
resumeRequests();
targetTracker.onStart();
}
public void onStop() {
pauseRequests();
targetTracker.onStop();
}
1.3 小結
經過上述分析,我們可以使用下面的流程圖總結 Glide 的 with()
方法的執行過程:
2、load() 方法的執行過程
2.1 load() 的過程
當我們拿到了 RequestManager
之後就可以使用它來呼叫 load()
方法了。在我們的示例中傳入的是一個 url 物件。load()
方法也是過載的,我們可以傳入包括 Bitmap, Drawable, Uri 和 String 等在內的多種資源型別。示例中會呼叫下面的這個方法得到一個 RequestBuilder
物件,顯然這是一種構建者模式的應用。我們可以使用 RequestBuilder
的其他方法來繼續構建圖片載入請求,你可以通過檢視它的原始碼瞭解 Glide 都為我們提供了哪些構建方法:
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
在 RequestBuilder
的構造方法中存在一個 apply()
方法值得我們一提,其定義如下。從下面的方法定義中可以看出,我們可以通過為 RequestBuilder
指定 RequestOptions
來配置當前圖片載入請求。比如,指定磁碟快取的策略,指定佔位圖,指定圖片加載出錯時顯示的圖片等等。那麼我們怎麼得到 RequestOptions
呢?在 Glide 4.8.0 中的類 RequestOptions
為我們提供了一系列的靜態方法,我們可以這些方法來得到 RequestOptions
的例項:
public RequestBuilder<TranscodeType> apply(RequestOptions requestOptions) {
Preconditions.checkNotNull(requestOptions);
this.requestOptions = getMutableOptions().apply(requestOptions);
return this;
}
回過頭來,我們可以繼續跟蹤 load()
方法。其實,不論我們使用了 load()
的哪個過載方法,最終都會呼叫到下面的方法。它的邏輯也比較簡單,就是將我們的圖片資源資訊賦值給 RequestBuilder
的區域性變數就完事了。至於圖片如何被載入和顯示,則在 into()
方法中進行處理。
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
2.2 小結
所以,我們可以總結 Glide 的 load()
方法的執行過程如下。也就是使用 RequestManger
得到一個 RequestBuilder
的過程:
3、into() 方法的執行過程
考慮到 into()
方法流程比較長、涉及的類比較多,我們按照圖片載入的過程將其分成四個階段來進行介紹。
第一個階段是開啟 DecodeJob
的過程。DecodeJob
負責從快取或者從原始的資料來源中載入圖片資源,對圖片進行變換和轉碼,是 Glide 圖片載入過程的核心。DecodeJob
繼承了 Runnable
,實際進行圖片載入的時候會將其放置到執行緒池當中執行。這個階段我們重點介紹的是從 RequestBuilder
構建一個 DecodeJob
並開啟 DecodeJob
任務的過程。即構建一個 DecodeJob
並將其丟到執行緒池裡的過程。
第二個階段是開啟網路流的過程。這個階段會根據我們的圖片資源來從資料來源中載入圖片資料。以我們的示例為例,在預設情況下會從網路當中載入圖片,並得到一個 InputStream
.
第三個階段是將輸入流轉換為 Drawable
的過程。得到了 InputStream
之後還要呼叫 BitmapFactory
的 decodeStream()
方法來從 InputStream
中得到一個 Drawable
.
第四個階段是將 Drawable
顯示到 ImageView
上面的過程。
3.1 階段1:開啟 DecodeJob 的過程
3.1.1 流程分析
我們繼續沿著 into()
方法進行分析。
into()
方法也定義在 RequestBuilder
中,並且也是過載的。不論我們呼叫哪個過載方法都會將要用來顯示圖片的物件封裝成一個 Target
型別。Target
主要用來對用來顯示圖片的物件的生命週期進行管理。當我們要將圖片載入到 ImageView 的時候,最終會呼叫下面的 buildTarget()
方法來講我們的 ImageView 封裝成一個 ViewTarget
,然後呼叫 into()
的過載方法進行後續處理:
public <Z> ViewTarget<ImageView, Z> buildTarget(ImageView view, Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
private <Y extends Target<TranscodeType>> Y into(Y target,
RequestListener<TranscodeType> targetListener,
RequestOptions options) {
options = options.autoClone();
Request request = buildRequest(target, targetListener, options); // 1
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
request.recycle();
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request); // 2
return target;
}
在上面的 into()
方法的 1
處最終會呼叫到下面的方法來構建一個請求物件。(這裡我們忽略掉具體的引數,只給看構建請求的邏輯)。簡而言之,該方法會根據我們是否呼叫過 RequestBuilder
的 error()
方法設定過圖片加載出錯時候顯示的圖片來決定返回 mainRequest
還是 errorRequestCoordinator
。因為我們沒有設定該引數,所以會直接返回 mainRequest
。
private Request buildRequestRecursive(/*各種引數*/) {
ErrorRequestCoordinator errorRequestCoordinator = null;
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}
Request mainRequest = buildThumbnailRequestRecursive(/*各種引數*/); // 1
if (errorRequestCoordinator == null) {
return mainRequest;
}
// ... 略
Request errorRequest = errorBuilder.buildRequestRecursive(/*各種引數*/);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}
上面是根據是否設定載入失敗時顯示的圖片來決定返回的請求物件的。如果你使用過 Glide 的話,那麼一定記得除了設定載入失敗時的圖片,我們還會先載入一張小圖,即 Thumbnail
。所以,在上面方法的 1
處會根據設定呼叫過 RequestBuilder
的 thumbnail()
方法來決定返回 Thumbnail
的請求還是真實圖片的請求。同樣因為我們沒有設定過該方法,所以最終會呼叫下面的方法來構建最終的圖片載入請求。
private Request obtainRequest(/*各種引數*/) {
return SingleRequest.obtain(/*各種引數*/);
}
在 SingleRequest
的 obtain()
方法中會先嚐試從請求的池中取出一個請求,當請求不存在的時候就會例項化一個 SingleRequest
,然後呼叫它的 init()
方法完成請求的初始化工作。這裡的請求池使用了 Android 的 support v4 包中的 Pool
相關的 API. 它被設計用來構建基於陣列的請求池,具體如何使用可以參考相關的文件和原始碼。
public static <R> SingleRequest<R> obtain(/*各種引數*/) {
SingleRequest<R> request = (SingleRequest<R>) POOL.acquire();
if (request == null) {
request = new SingleRequest<>();
}
request.init(/*各種引數*/);
return request;
}
得到了請求之後會用 RequestManager
的 track()
方法:
void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
該方法的主要作用有兩個:
- 呼叫
TargetTracker
的track()
方法對對當前Target
的生命週期進行管理; - 呼叫
RequestTracker
的runRequest()
方法對當前請求進行管理,當 Glide 未處於暫停狀態的時候,會直接使用Request
的begin()
方法開啟請求。
下面是 SingeleRequest
的 begin()
方法。它會根據當前載入的狀態來判斷應該呼叫哪個方法。因為我們之前圖片載入的過程可能因為一些意想不到的原因被終止,所以當重啟的時候就需要根據之前的狀態進行恢復。對於我們第一次載入的情況,則會直接進入到下方 1 處的 onSizeReady()
方法中:
public void begin() {
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;
}
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
// 如果我們在完成之後重新啟動(通常通過諸如 notifyDataSetChanged() 之類的方法,
// 在相同的目標或檢視中啟動相同的請求),我們可以使用我們上次檢索的資源和大小
// 並跳過獲取新的大小。所以,如果你因為 View 大小發生了變化而想要重新載入圖片
// 就需要在開始新載入之前清除檢視 (View) 或目標 (Target)。
if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight); // 1
} else {
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable()); // 2
}
}
下面是 onSizeReady()
方法,我們可以看出它會先判斷當前是否處於 Status.WAITING_FOR_SIZE
狀態,並隨後將狀態更改為 Status.RUNNING
並呼叫 engine
的 load()
方法。顯然,更改完狀態之後繼續回到上面的方法,在 2 處即呼叫了 Target
的 onLoadStarted()
方法。這樣 Target
的第一個生命週期就被觸發了。
public void onSizeReady(int width, int height) {
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
loadStatus = engine.load(/*各種引數*/);
if (status != Status.RUNNING) {
loadStatus = null;
}
}
然後,讓我們將重點放到 Engine
的 load()
方法。該方法雖然不長,但是卻包含了許多重要的內容。我們在下篇文章中將要研究的 Glide 的快取就是在這裡實現的。該方法大致的邏輯上,先嚐試從記憶體快取當中查詢指定的資源,當記憶體中不存在的時候就準備使用 DecodeJob
來載入圖片。
public <R> LoadStatus load(/*各種引數*/) {
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob = engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob = decodeJobFactory.build(/*各種引數*/);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
上面方法中涉及兩個類,一個是 DecodeJob
、一個是 EngineJob
。它們之間的關係是,EngineJob
內部維護了執行緒池,用來管理資源載入,已經當資源載入完畢的時候通知回撥。 DecodeJob
繼承了 Runnable
,是執行緒池當中的一個任務。就像上面那樣,我們通過呼叫 engineJob.start(decodeJob)
來開始資源載入。
3.1.2 小結
根據上文中的分析,我們不難得出上面的流程圖。不考慮快取的問題,這個部分的邏輯還是比較清晰的,即:當呼叫了 into()
之後,首先構建一個請求物件 SingleRequest
,然後呼叫 RequestManager
的 track()
方法對 Request
和 Target
進行管理;隨後,使用 Request
的 begin()
方法來啟動請求;該方法中會使用 Engine
的 load()
方法決定是從快取當中獲取資源還是從資料來源中載入資料;如果是從資料來源中載入資料的話,就構建一個 DecodeJob
交給 EngineJob
來執行即可。
3.2 階段2:開啟網路流的過程
3.2.1 開啟網路流的過程
在上面的分析中,將 DecodeJob
交給 EngineJob
就完事了。因為 DecodeJob
是一個任務,會線上程池當中進行執行。所以,如果我們繼續追蹤的話,就應該從 DecodeJob
的 run()
方法開始:
所以,如果想要找到載入資源和解碼的邏輯,就應該檢視 DecodeJob 的 run()
方法。下面就是這個方法的定義:
public void run() {
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch