Glide的圖片加載流程
上一章節(jié)Android圖片加載框架Glide源碼解析(一)中講Glide的基本使用卵渴,對于不會使用的建議先去看下劲件。
體驗了Glide的便捷及強大后丑掺,我們一定很好奇Glide是如何加載圖片的或者說加載流程又是怎樣的呢谷浅?不用急秘血,本章我們就從源碼上一步步分析加載流程即彪。
1紧唱、Glide一次調(diào)用的代碼如下:
Glide.with(context)
.load(url)
.apply(RequestOptions.placeholderOf(R.drawable.ic_default)
.error(R.drawable.ic_error))
.thumbnail(0.4f)
.into(imageview);
2、從調(diào)用流程入手進行分析
1.調(diào)用with()靜態(tài)方法
Glide類本身是一個單例隶校,調(diào)用靜態(tài)方法with()去實例化Glide類并返回RequestManager對象漏益,以供后面使用。靜態(tài)方法with()是一個重載方法惠况,參數(shù)可以是Context遭庶、Activity、Fragment稠屠、View峦睡、FragmentActivity這五種,源碼如下:
/**
* 傳入對象是Context,
*/
@NonNull
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
//傳入對象是Activity
@NonNull
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
//傳入對象是FragmentActivity
@NonNull
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
//傳入對象是Fragment
@NonNull
public static RequestManager with(@NonNull Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
//傳入對象是View
@NonNull
public static RequestManager with(@NonNull View view) {
return getRetriever(view.getContext()).get(view);
}
從源碼可以看到权埠,這幾個重載方法內(nèi)部均調(diào)用了同一個靜態(tài)方法getRetriever()榨了,那么這個方法是干嘛用的呢,看源碼:
@NonNull
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Context could be null for other reasons (ie the user passes in null), but in practice it will
// only occur due to errors with the Fragment lifecycle.
Preconditions.checkNotNull(
context,
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
}
從源碼我們可以看到攘蔽,先去對參數(shù)context進行非空檢查龙屉,然后調(diào)用Glide的靜態(tài)方法get()采用懶漢式的方式實現(xiàn)單例,通過Builder模式實例化Glide對象满俗,由于篇幅這里就不貼初始化的代碼了转捕。初始化后調(diào)用getRequestManagerRetriever()得到RequestManagerRetriever對象,然后調(diào)用get()就能獲得一個RequestManager對象唆垃。
2.通過RequestManager類管理Request請求
RequestManager類是實現(xiàn)Glide加載資源的管理類五芝,根據(jù)Activity生命周期管控資源加載請求,可以開啟辕万、暫停及重啟枢步。
通過調(diào)用load()重載方法創(chuàng)建一個RequestBuilder沉删,去加載資源。
@NonNull
@CheckResult
@Override
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
其中,asDrawable方法主要是設(shè)置加載的資源最終以Drawable對象返回醉途,默認情況下矾瑰,Glide加載圖片資源時以Drawable對象對象,目前Glide支持返回Drawable隘擎、Bitmap殴穴、gif和File四種,在不想使用默認的drawable返回的情況下可以在調(diào)用load前調(diào)用對應(yīng)的方法货葬,如Bitmap對應(yīng)asBitmap();
Glide.with(context)
.asBitmap() //該句表示加載資源后保存為Bitmap對象
.load(url)
.into(imageview);
注:如果調(diào)用asBitmap()后在調(diào)用load方法推正,此時調(diào)用的是RequestBuilder類的load方法,注意區(qū)分1Χ琛!再沧!
好了尼夺,回到正題,上述調(diào)用load后內(nèi)部是通過asDrawable()設(shè)置資源返回類型炒瘸,并返回一個RequestBuilder對象淤堵,然后調(diào)用RequestBuilder的方法load(),看一下源碼:
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
可以看到這里new了一個requestBuilder對象顷扩。
3.RequestOptions實現(xiàn)各種選項設(shè)置
通過上述返回requestBuilder對象后拐邪,后續(xù)的加載資源及一些設(shè)置就在requestBuilder類中實現(xiàn)了。在V4版本中隘截,Glide將一些選項設(shè)置模塊劃分出來扎阶,放在RequestOptions類中,其中包括設(shè)置占位圖婶芭、錯誤圖东臀、緩存機制、關(guān)閉動畫犀农、scaleType等惰赋。在調(diào)用apply()方法時傳入RequestOptions對象,apply()方法源碼如下:
//傳入RequestOptions類對象呵哨,并保存起來
public RequestBuilder<TranscodeType> apply(@NonNull RequestOptions requestOptions) {
Preconditions.checkNotNull(requestOptions);
this.requestOptions = getMutableOptions().apply(requestOptions);
return this;
}
//判斷默認選項和設(shè)置的是否一致,如果一致返回this.requestOptions的一個克隆赁濒,如果不一致則直接返回 this.requestOptions
protected RequestOptions getMutableOptions() {
return defaultRequestOptions == this.requestOptions
? this.requestOptions.clone() : this.requestOptions;
}
獲取到當(dāng)前的RequestOptions對象后,通過調(diào)用對象方法的apply方法孟害,將上傳入的requestOptions對象傳入并保持設(shè)置拒炎,方便后續(xù)使用,同時返回了新的RequestOptions對象賦值給this.requestOptions變量纹坐,下面是RequestOptions類中的apply()方法:
public RequestOptions apply(@NonNull RequestOptions other) {
//允許自動克隆枝冀,則直接克隆一個RequestOptions對象然后設(shè)置類各種屬性舞丛,并直接返回該對象
if (isAutoCloneEnabled) {
return clone().apply(other);
}
...
中間省略代碼主要用于判斷是否設(shè)置了對應(yīng)的屬性(占位圖、錯誤果漾、緩存等選項)球切,沒設(shè)置過就去設(shè)置,由于代碼量大绒障,這里不貼出了
...
// Applying options with dontTransform() is expected to clear our transformations.
//這里檢查是否要動畫效果吨凑,如果不需要,就清除所有過場動畫效果
if (!isTransformationAllowed) {
transformations.clear();
fields &= ~TRANSFORMATION;
isTransformationRequired = false;
fields &= ~TRANSFORMATION_REQUIRED;
isScaleOnlyOrNoTransform = true;
}
fields |= other.fields;
options.putAll(other.options);
return selfOrThrowIfLocked();
}
Glide加載圖片資源中的一些選項設(shè)置都在這個RequestOptions類中户辱,該類也提供了一些選項設(shè)置的靜態(tài)方法鸵钝,設(shè)置完成直接返回requestOptions對象,不需要用戶自己去new庐镐。
4.調(diào)用RequestBuilder類中into()方法實現(xiàn)資源加載并設(shè)置到目標target中
前面幾點講述了資源加載之前的基礎(chǔ)設(shè)置及選項設(shè)置恩商,最后調(diào)用into實現(xiàn)資源的加載。into()方法時個重載類必逆,最終幾個方法都會進入到into(Y target,RequestListener<TranscodeType> targetListener,RequestOptions options)中怠堪,不廢話,直接看源碼:
private <Y extends Target<TranscodeType>> Y into(@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options) {
Util.assertMainThread();//檢測是否在主線程名眉,非主線程調(diào)用時拋異常
Preconditions.checkNotNull(target);//判斷target非空
//防止在load之前調(diào)用into方法
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
options = options.autoClone();
//通過buildRequest()方法構(gòu)建一個Request對象
Request request = buildRequest(target, targetListener, options);
//獲取target之前的request請求對象
Request previous = target.getRequest();
//將新構(gòu)建的request對象與舊的previous對象進行比較粟矿,判斷是否相同。
// isSkipMemoryCacheWithCompletePreviousRequest用于判斷options是否有使用緩存機制和previous是否已經(jīng)加載完畢
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
request.recycle();
//判斷previous是否正在運行损拢,如果沒在運行陌粹,則執(zhí)行previous這個請求
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
into方法中主要做了以下兩個方面的事情:
通過buildRequest()方法構(gòu)建一個Request;
-
獲取target的Request對象,與構(gòu)建的Request對象進行比較福压,并檢測是否采用緩存及target的上次請求是否已經(jīng)完成:
1.兩個對象相等郑藏,并且options采用了緩存或target的上次請求未完成入偷,則將構(gòu)建的request進行回收歹叮,并且 判斷target的request是否正在運行包颁,正在運行則直接返回target,若為正在運行則調(diào)用Request的begin方法 啟動同步加載資源請求胞枕;
2.兩個對象不等杆煞,或者option沒使用緩存機制和target的請求已經(jīng)完成,清除target之前的設(shè)置腐泻,并將新構(gòu)建的request對象設(shè)置給target决乎,通過track方法啟動Request請求;
buildRequest()方法源碼://構(gòu)建一個Request對象 private Request buildRequest(Target<TranscodeType> target, @Nullable RequestListener<TranscodeType> targetListener,RequestOptions requestOptions) { return buildRequestRecursive(target,targetListener, /*parentCoordinator=*/ null, transitionOptions, requestOptions.getPriority(), requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight(), requestOptions); } /** * 主要做的事情就是先創(chuàng)建一個主加載資源的Request對象派桩,然后根據(jù)是否設(shè)置了加載錯誤時 * 顯示資源選項构诚,創(chuàng)建一個errorRequest加載資源對象,最后返回Request **/ private Request buildRequestRecursive(Target<TranscodeType> target, @Nullable RequestListener<TranscodeType> targetListener, @Nullable RequestCoordinator parentCoordinator, TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, RequestOptions requestOptions) { // Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator. ErrorRequestCoordinator errorRequestCoordinator = null; if (errorBuilder != null) { errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator); parentCoordinator = errorRequestCoordinator; } //創(chuàng)建加載資源的Request請求 Request mainRequest = buildThumbnailRequestRecursive( target, targetListener, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight, requestOptions); if (errorRequestCoordinator == null) { return mainRequest; } int errorOverrideWidth = errorBuilder.requestOptions.getOverrideWidth(); int errorOverrideHeight = errorBuilder.requestOptions.getOverrideHeight(); if (Util.isValidDimensions(overrideWidth, overrideHeight) && !errorBuilder.requestOptions.isValidOverride()) { errorOverrideWidth = requestOptions.getOverrideWidth(); errorOverrideHeight = requestOptions.getOverrideHeight(); } Request errorRequest = errorBuilder.buildRequestRecursive( target, targetListener, errorRequestCoordinator, errorBuilder.transitionOptions, errorBuilder.requestOptions.getPriority(), errorOverrideWidth, errorOverrideHeight, errorBuilder.requestOptions); errorRequestCoordinator.setRequests(mainRequest, errorRequest); return errorRequestCoordinator; }
isSkipMemoryCacheWithCompletePreviousRequest方法源碼:
private boolean isSkipMemoryCacheWithCompletePreviousRequest( RequestOptions options, Request previous) {
return !options.isMemoryCacheable() && previous.isComplete();
}
當(dāng)條件不滿足時铆惑,先執(zhí)行clear范嘱,在執(zhí)行track方法送膳,實現(xiàn)Request請求,源碼如下:
void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
該方法丑蛤,將target加入到targetTracker列表中叠聋,然后運行Request,看下runRequest方法:
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);
}
}
代碼很簡單受裹,一看就懂碌补。
到目前為止一次Glide加載圖片資源的流程已經(jīng)完成大半了,后面就是看Request如何加載資源并顯示到target的棉饶。
5.Request實現(xiàn)加載資源及顯示
上面講到創(chuàng)建了Request對象厦章,但是Request對象是一個interface,那么Request是在哪里實現(xiàn)的呢照藻?追蹤上面創(chuàng)建的Request對象袜啃,很容易在RequestBuilder中發(fā)現(xiàn)obtainRequest方法,該方法如下:
private Request obtainRequest( Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,RequestOptions requestOptions,
RequestCoordinator requestCoordinator,TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,int overrideWidth,int overrideHeight) {
return SingleRequest.obtain(context,glideContext,model,transcodeClass,requestOptions,
overrideWidth,overrideHeight,priority,target,
targetListener, requestListeners, requestCoordinator, glideContext.getEngine(),
transitionOptions.getTransitionFactory());
}
該方法通過SingleRequest類的方法obtain去創(chuàng)建一個Request對象幸缕,該類實現(xiàn)了Request囊骤、ResourceCallback、SizeReadyCallback等接口冀值,可以說這個類就是我們要找的。那么我們看下該類如何實現(xiàn)begin方法的宫屠,首先看下源碼:
@Override
public void begin() {
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);
return;
}
// Restarts for requests that are neither complete nor running can be treated as new requests
// and can run again from the beginning.
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));
}
}
方法經(jīng)過一系列的條件判斷列疗,當(dāng)status是Running或者WAITING_FOR_SIZE狀態(tài),并且需要設(shè)置占位圖的時候浪蹂,會調(diào)用onLoadStarted設(shè)置占位圖抵栈,當(dāng)status是Status.COMPLETE時會執(zhí)行ResouceCallback接口的onResourceReady方法,繼續(xù)看代碼:
public void onResourceReady(Resource<?> resource, DataSource dataSource) {
stateVerifier.throwIfRecycled();
loadStatus = null;
if (resource == null) {
GlideException exception = new GlideException("Expected to receive a Resource<R> with an "
+ "object of " + transcodeClass + " inside, but instead got null.");
//加載失敗
onLoadFailed(exception);
return;
}
Object received = resource.get();
if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
releaseResource(resource);
GlideException exception = new GlideException("Expected to receive an object of "
+ transcodeClass + " but instead" + " got "
+ (received != null ? received.getClass() : "") + "{" + received + "} inside" + " "
+ "Resource{" + resource + "}."
+ (received != null ? "" : " " + "To indicate failure return a null Resource "
+ "object, rather than a Resource object containing null data."));
//加載失敗
onLoadFailed(exception);
return;
}
//判斷是否需要設(shè)置資源坤次,如果不需要設(shè)置則直接釋放資源并設(shè)置狀態(tài)為complete
if (!canSetResource()) {
releaseResource(resource);
// We can't put the status to complete before asking canSetResource().
status = Status.COMPLETE;
return;
}
onResourceReady((Resource<R>) resource, (R) received, dataSource);
}
方法主要對條件判斷古劲,對于不符合條件的直接跑出異常并調(diào)用onLoadFailed回調(diào)失敗結(jié)果,成功了后先判斷是否需要設(shè)置資源缰猴,不設(shè)置的話就吧資源釋放然后設(shè)置狀態(tài)為complete产艾,否則就調(diào)用onResourceReady()的重載方法進一步處理,
private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
// We must call isFirstReadyResource before setting status.
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;
...
isCallingCallbacks = true;
try {
boolean anyListenerHandledUpdatingTarget = false;
if (requestListeners != null) {
for (RequestListener<R> listener : requestListeners) {
anyListenerHandledUpdatingTarget |=
listener.onResourceReady(result, model, target, dataSource, isFirstResource);
}
}
anyListenerHandledUpdatingTarget |=
targetListener != null
&& targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);
//設(shè)置資源及動畫相關(guān)信息
if (!anyListenerHandledUpdatingTarget) {
Transition<? super R> animation =
animationFactory.build(dataSource, isFirstResource);
target.onResourceReady(result, animation);
}
} finally {
isCallingCallbacks = false;
}
//最后通知加載成功
notifyLoadSuccess();
}
可以看到有這么一句:
target.onResourceReady(result, animation);
其實這里就是設(shè)置target資源,target分別與要設(shè)置資源的組件有關(guān)滑绒,比如Imageview闷堡,對應(yīng)ImageViewTarget,Glide中對應(yīng)如下幾個Target:
以ImageViewTarget類為例,onReourceReady()方法的實現(xiàn)如下:
@Override
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) {
// Order matters here. Set the resource first to make sure that the Drawable has a valid and
// non-null Callback before starting it.
setResource(resource);
maybeUpdateAnimatable(resource);
}
protected abstract void setResource(@Nullable Z resource);
那么setResource()抽象方法是在哪里實現(xiàn)的呢疑故,這就要根據(jù)設(shè)置資源的返回類型決定了杠览,比如我們的返回類型是Drawable,則實現(xiàn)是在DrawableImageViewTarget類中纵势,實現(xiàn)如下:
/**
* A target for display {@link Drawable} objects in {@link ImageView}s.
*/
public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
...
@Override
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
}
好了終于看到顯示資源的地方了踱阿。那么我們回到SingleRequest類的onResourceReady(Resource<R> resource, R result, DataSource dataSource)方法中管钳,最后又一句:
notifyLoadSuccess();
這個方法時做什么的呢,先看notifyLoadSuccess()的源碼:
private void notifyLoadSuccess() {
if (requestCoordinator != null) {
requestCoordinator.onRequestSuccess(this);
}
}
方法很簡單软舌,就是判斷下requestCoordinator 非空才漆,然后執(zhí)行requestCoordinator對象方法onRequestSuccess(),那么requestCoordinator的實現(xiàn)在哪里呢葫隙,搜索很容易發(fā)現(xiàn)繼承RequestCoordinator接口的只有兩個類ThumbnailRequestCoordinator和ErrorRequestCoordinator栽烂,打開ThumbnailRequestCoordinator類,找到onRequestSuccess方法恋脚,可以看到:
@Override
public void onRequestSuccess(Request request) {
if (request.equals(thumb)) {
return;
}
if (parent != null) {
parent.onRequestSuccess(this);
}
// Clearing the thumb is not necessarily safe if the thumb is being displayed in the Target,
// as a layer in a cross fade for example. The only way we know the thumb is not being
// displayed and is therefore safe to clear is if the thumb request has not yet completed.
if (!thumb.isComplete()) {
thumb.clear();
}
}
從源碼中看到這個方法主要做一些加載成功后的清理工作腺办。
由此可以看出,Glide加載成功后糟描,分別由Target和RequestCoordinator接管資源展示和request回收工作怀喉。
3、總結(jié)
源碼分析完了船响,我們總結(jié)出下面一個簡易的加載流程圖躬拢。
總結(jié)
- 1.Glide類作為入口類,調(diào)用靜態(tài)方法with(),實例化了Glide這個單例见间,取得RequestManager對象并返回聊闯;
- 2.RequestManager去管理Request請求,根據(jù)Activity或Fragment生命周期米诉,管理request請求菱蔬,包括:onStop,onStart,onDestory,pauseRequest,resumeRequests等,并可以設(shè)置加載資源要返回的資源類型史侣,Bitmap拴泌,Drawable,Gif。通過調(diào)用load()惊橱、asBitmap()蚪腐、asFile()等方法均返回一個RquestBuilder對象,將資源的請求處理移交給了RequestBuilder税朴,實現(xiàn)將功能分離回季,模塊化處理。
- 3.RquestBuilder處理資源加載請求正林,調(diào)用apply()方法傳入RequestOptions對象進行一些選項設(shè)置;調(diào)用transition()方法實現(xiàn)資源加載顯示時過度動畫;最后調(diào)用into()方法茧跋,去構(gòu)建Rquest對象或使用Target設(shè)置過的Request對象,調(diào)用Rquest的對象方法begin()開啟加載請求卓囚。
- 4.開啟請求后的工作就移交到Request類中瘾杭,加載完成后,Request調(diào)用onResourceReady()方法去判斷加載結(jié)果哪亿,若加載成功粥烁,調(diào)用Target類的onReadySuccess()方法設(shè)置資源贤笆,調(diào)用RequestCoordinator中方法onRequestSuccess()執(zhí)行加載成功后的資源清理工作。若加載失敗讨阻,Request直接調(diào)用notifyLoadFailed()方法芥永,將清理資源工作移交給RequestCoordinator類處理。
參考資源:
Glide源碼