Glide原始碼解析-載入流程
一直想要閱讀Glide原始碼,但是苦於時間和功力都不夠,總是斷斷續續的,趁著現在有一些空暇時間,來簡要分析Glide的原始碼。Glide的實現太過複雜,不可能做到面面俱到,如果每一行都細緻分析,很容易陷入作者的優化細節中去而偏離主線,因此只針對幾個主要功能做解析即可。 以下分析全部基於Glide v4.9.0。
2 初始化
Glide最常見的用法就是如下一行程式碼:
Glide.with(context).load(url).into(imageView); 複製程式碼
一步步來分析如何將url圖片載入到imageview上來。
2.1 with
public static RequestManager with(@NonNull Context context) { return getRetriever(context).get(context); } public static RequestManager with(@NonNull Activity activity) { return getRetriever(activity).get(activity); } public static RequestManager with(@NonNull FragmentActivity activity) { return getRetriever(activity).get(activity); } public static RequestManager with(@NonNull Fragment fragment) { return getRetriever(fragment.getActivity()).get(fragment); } public static RequestManager with(@NonNull View view) { return getRetriever(view.getContext()).get(view); } 複製程式碼
with方法是Glide類中的一組同名static過載函式,可以傳入多種上下文,方法體內是呼叫getRetriever
獲得RequestManagerRetriever
例項物件,再呼叫其get方法返回一個RequestManager
例項。
private static volatile Glide glide; 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. ... return Glide.get(context).getRequestManagerRetriever(); } public static Glide get(@NonNull Context context) { if (glide == null) { synchronized (Glide.class) { if (glide == null) { checkAndInitializeGlide(context); } } } return glide; } public RequestManagerRetriever getRequestManagerRetriever() { return requestManagerRetriever; } 複製程式碼
這裡Glide的get方法用了DCL單例,然後拿到Glide的成員變數requestManagerRetriever。 然後再看RequestManagerRetriever類。
@NonNull 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); } 複製程式碼
這是上文with方法裡呼叫的get方法,這裡會對傳入的context做判斷,如果方法呼叫是在主執行緒同時context不是Application,則會根據context的型別呼叫一組過載的get方法,否則就呼叫getApplicationManager。
那這兩個分支有什麼區別呢?具體看一下兩個處理方法。
private RequestManager getApplicationManager(@NonNull Context context) { // Either an application context or we're on a background thread. if (applicationManager == null) { synchronized (this) { if (applicationManager == null) { // Normally pause/resume is taken care of by the fragment we add to the fragment or // activity. However, in this case since the manager attached to the application will not // receive lifecycle events, we must force the manager to start resumed using // ApplicationLifecycle. // TODO(b/27524013): Factor out this Glide.get() call. Glide glide = Glide.get(context.getApplicationContext()); applicationManager = factory.build( glide, new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(), context.getApplicationContext()); } } } return applicationManager; } public RequestManager get(@NonNull FragmentActivity activity) { if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); FragmentManager fm = activity.getSupportFragmentManager(); return supportFragmentGet( activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); } } private SupportRequestManagerFragment getSupportRequestManagerFragment( @NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) { SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); if (current == null) { current = pendingSupportRequestManagerFragments.get(fm); if (current == null) { current = new SupportRequestManagerFragment(); current.setParentFragmentHint(parentHint); if (isParentVisible) { current.getGlideLifecycle().onStart(); } pendingSupportRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget(); } } return current; } 複製程式碼
這裡的區別主要是將glide和不同的上下文的宣告週期繫結,如果是Application或者不在主執行緒呼叫,那requetmanager的生命週期和Application相關,否則則會和當前頁面的fragmentManager的宣告週期相關。因為Activity下fragmentManager的生命週期和Activity相同。所以不管是Activity還是fragment,最後都會委託給fragmentManager做生命週期的管理。
在getSupportRequestManagerFragment方法中可以看到如果activity下的fragmentmanager沒有找到tag為FRAGMENT_TAG的fragment,就會建立一個隱藏的fragment,然後新增到fragmentmanager內。
總結來說with方法的作用就是獲得當前上下文,構造出和上下文生命週期繫結的requestmanager,自動管理glide的載入開始和停止。
2.2 load
load方法也是一組過載方法,定義在interface ModelTypes<T>
接口裡,這是一個泛型介面,規定了load想要返回的資料型別,RequestManager
類實現了該介面,泛型為Drawable類。
public RequestBuilder<Drawable> asDrawable() { return as(Drawable.class); } public RequestBuilder<Drawable> load(@Nullable File file) { return asDrawable().load(file); } public RequestBuilder<Drawable> load(@Nullable Uri uri) { return asDrawable().load(uri); } public RequestBuilder<Drawable> load(@Nullable String string) { return asDrawable().load(string); } public RequestBuilder<Drawable> load(@Nullable Drawable drawable) { return asDrawable().load(drawable); } public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) { return asDrawable().load(bitmap); } public RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId) { return asDrawable().load(resourceId); } public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class<ResourceType> resourceClass) { return new RequestBuilder<>(glide, this, resourceClass, context); } ...... 複製程式碼
RequestManager下的load方法都返回RequestBuilder物件,顯然是一個建造者模式,用來構建需要的屬性。asDrawable方法呼叫的as方法實際上是呼叫RequestBuilder的構造方法。然後掉用RequestBuilder的load將需要載入的object傳遞給builder,然後load方法都會呼叫loadGeneric將不同的引數型別統一傳給Object類的model成員變數,如果傳遞的引數型別是Drawable或者Bitmap,那麼將會額外呼叫.apply(diskCacheStrategyOf(DiskCacheStrategy.NONE));
,意味著這兩種型別將不會做快取。
public RequestBuilder<TranscodeType> load(@Nullable Bitmap bitmap) { return loadGeneric(bitmap) .apply(diskCacheStrategyOf(DiskCacheStrategy.NONE)); } private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { this.model = model; isModelSet = true; return this; } 複製程式碼
總結一下load作用,構造一個RequestBuilder例項,同時傳入需要載入的資料來源型別。
2.3 into
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { Util.assertMainThread(); Preconditions.checkNotNull(view); BaseRequestOptions<?> requestOptions = this; //檢查是否額外設定了imageview的裁剪方法 if (!requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() != null) { 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( //構建target glideContext.buildImageViewTarget(view, transcodeClass), /*targetListener=*/ null, requestOptions, Executors.mainThreadExecutor()); } @NonNull public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) { return into(target, /*targetListener=*/ null, Executors.mainThreadExecutor()); } @NonNull @Synthetic <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, Executor callbackExecutor) { return into(target, targetListener, /*options=*/ this, callbackExecutor); } 複製程式碼
into方法同樣是在RequestBuilder內,是glide載入圖片流程的最後一步,他暴露了兩種public方法,一個的引數是imageview,作用是指定圖片最後要載入到的位置。另一個引數是target物件,可以定製化一個target並返回。
對於引數imageview的into方法,首先先檢查requestBuilder是否額外設定過imageview的scaletype屬性,如果有則在requestoption裡面加上裁剪選項,接著構建一個target例項並建立一個主執行緒的executor用於獲得圖片資源在主執行緒更新UI,呼叫private的into方法。
建立target方法細節如下:
public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) { //工廠方法建立target的子類viewtarget return imageViewTargetFactory.buildTarget(imageView, transcodeClass); } public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view, @NonNull Class<Z> clazz) { //根據傳入的不同class型別構造bitmap或者drawabletarget 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)"); } } public class DrawableImageViewTarget extends ImageViewTarget<Drawable> { public DrawableImageViewTarget(ImageView view) { super(view); } /** * @deprecated Use {@link #waitForLayout()} instead. */ // Public API. @SuppressWarnings({"unused", "deprecation"}) @Deprecated public DrawableImageViewTarget(ImageView view, boolean waitForLayout) { super(view, waitForLayout); } //負責將最後獲得Drawable資源載入到into指定的imageview上 @Override protected void setResource(@Nullable Drawable resource) { view.setImageDrawable(resource); } } 複製程式碼
對於引數target的into方法,則是直接建立一個主執行緒的runnable用於回撥target給主執行緒。
接下來看private的into方法做了什麼:
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> options, Executor callbackExecutor) { Preconditions.checkNotNull(target); //判斷是否呼叫過load方法設定目資源 if (!isModelSet) { throw new IllegalArgumentException("You must call #load() before calling #into()"); } //構建Request例項 Request request = buildRequest(target, targetListener, options, callbackExecutor); 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繫結request target.setRequest(request); //執行request requestManager.track(target, request); return target; } 複製程式碼
先判斷是否呼叫過load方法設定目標資源變數,如果沒有直接丟擲異常,接著構建request例項,同時獲得target上的前一個request(引數為imageview的into方法跳過),如果相同則直接複用前一個request,免去了一些配置步驟,同時為了能順利完成回撥,增加了重試機制。然後對於imageview來說會先取消之前掛起的任務清楚任務資源,然後為target重新繫結request請求,track方法開始執行request任務。
看一下最後track方法做了什麼:
synchronized void track(@NonNull Target<?> target, @NonNull Request request) { //將target加入到追蹤佇列 targetTracker.track(target); //執行request請求 requestTracker.runRequest(request); } 複製程式碼
首先會將目標target加入到追蹤佇列,這個佇列裡儲存了當前activity裡所有的target,同時和生命週期進行了繫結,這樣做的好處是用生命週期自動管理了request請求的開始、暫停、結束等操作。
public final class TargetTracker implements LifecycleListener { private final Set<Target<?>> targets = Collections.newSetFromMap(new WeakHashMap<Target<?>, Boolean>()); public void track(@NonNull Target<?> target) { targets.add(target); } public void untrack(@NonNull Target<?> target) { targets.remove(target); } @Override public void onStart() { for (Target<?> target : Util.getSnapshot(targets)) { target.onStart(); } } @Override public void onStop() { for (Target<?> target : Util.getSnapshot(targets)) { target.onStop(); } } @Override public void onDestroy() { for (Target<?> target : Util.getSnapshot(targets)) { target.onDestroy(); } } @NonNull public List<Target<?>> getAll() { return Util.getSnapshot(targets); } public void clear() { targets.clear(); } } 複製程式碼
runRequest方法則是最終真正執行載入圖片資源的操作:
public void runRequest(@NonNull Request request) { requests.add(request); //request佇列是否處於暫停狀態 if (!isPaused) { //如果不則執行request request.begin(); } else { //清楚request資源 request.clear(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Paused, delaying request"); } //加入掛起佇列 pendingRequests.add(request); } } 複製程式碼
begin操作是呼叫了SingleRequest類的begin方法,SingleRequest實現了Request介面:
public synchronized void begin() { assertNotCallingCallbacks(); stateVerifier.throwIfRecycled(); startTime = LogTime.getLogTime(); //如果 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; } //正在執行 重複執行Request丟擲異常 if (status == Status.RUNNING) { throw new IllegalArgumentException("Cannot restart a running request"); } //如果我們在完成之後重新啟動(通常通過諸如notifyDataSetChanged之類的東西,在相同的目標或檢視中啟動相同的請求),我們可以簡單地使用我們上次檢索的資源和大小,並跳過獲取新的大小 //這意味著想要重新啟動負載因為期望檢視大小已更改的使用者需要在開始新載入之前明確清除檢視或目標。 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()); } } 複製程式碼
方法裡首先判斷是否設定過model,如果沒有則直接回調載入失敗,然後判斷是否正在執行,如果重複請求就直接丟擲異常,接著判斷是否Request已經完成,完成則呼叫onResourceReady
,接著給view確定height和width,同時呼叫onSizeReady
,如果狀態處於running或者WAITING_FOR_SIZE,呼叫onLoadStarted
。
這三個回撥的名字很明顯,分別對應Request的三個過程,onSizeReady(準備)、onLoadStarted(開始)、onResourceReady(資源完成),一個個來看。
2.3.1 onSizeReady
public synchronized void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled(); //如果進入時不是WAITING_FOR_SIZE直接退出 if (status != Status.WAITING_FOR_SIZE) { return; } //狀態調整至running status = Status.RUNNING; float sizeMultiplier = requestOptions.getSizeMultiplier(); this.width = maybeApplySizeMultiplier(width, sizeMultiplier); this.height = maybeApplySizeMultiplier(height, sizeMultiplier); loadStatus = engine.load( glideContext, model, requestOptions.getSignature(), this.width, this.height, requestOptions.getResourceClass(), transcodeClass, priority, requestOptions.getDiskCacheStrategy(), requestOptions.getTransformations(), requestOptions.isTransformationRequired(), requestOptions.isScaleOnlyOrNoTransform(), requestOptions.getOptions(), requestOptions.isMemoryCacheable(), requestOptions.getUseUnlimitedSourceGeneratorsPool(), requestOptions.getUseAnimationPool(), requestOptions.getOnlyRetrieveFromCache(), this, callbackExecutor); if (status != Status.RUNNING) { loadStatus = null; } } 複製程式碼
load方法就是真正執行載入資源的程式碼,裡面有一個runWrapped方法:
private void runWrapped() { switch (runReason) { case INITIALIZE: //獲取下一個狀態 stage = getNextStage(Stage.INITIALIZE); currentGenerator = getNextGenerator(); runGenerators(); break; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break; case DECODE_DATA: decodeFromRetrievedData(); break; default: throw new IllegalStateException("Unrecognized run reason: " + runReason); } } private enum Stage { /** The initial stage. */ INITIALIZE, /** Decode from a cached resource. */ RESOURCE_CACHE, /** Decode from cached source data. */ DATA_CACHE, /** Decode from retrieved source. */ SOURCE, /** Encoding transformed resources after a successful load. */ ENCODE, /** No more viable stages. */ FINISHED, } 複製程式碼
這裡做了一個狀態機的轉換,按照Stage的流程不斷的流轉,如果runReason是INITIALIZE,就獲取Stage.INITIALIZE的下一個狀態,先從RESOURCE_CACHE記憶體裡獲取快取,再從DATA_CACHE磁盤獲取快取,再從source資料來源取資料。這就是三級快取的策略。
三級快取的生成對應著三個生成類,通過呼叫getNextGenerator方法獲取:
private DataFetcherGenerator getNextGenerator() { switch (stage) { case RESOURCE_CACHE: return new ResourceCacheGenerator(decodeHelper, this); case DATA_CACHE: return new DataCacheGenerator(decodeHelper, this); case SOURCE: return new SourceGenerator(decodeHelper, this); case FINISHED: return null; default: throw new IllegalStateException("Unrecognized stage: " + stage); } } 複製程式碼
獲取到DataFetcherGenerator之後,就會呼叫runGenerators方法去執行獲取資料操作,startNext方法就是內部獲取資料程式碼。
private void runGenerators() { currentThread = Thread.currentThread(); startFetchTime = LogTime.getLogTime(); boolean isStarted = false; while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator(); if (stage == Stage.SOURCE) { reschedule(); return; } } // We've run out of stages and generators, give up. if ((stage == Stage.FINISHED || isCancelled) && !isStarted) { notifyFailed(); } } 複製程式碼
來看一下SourceGenerator的startNext(),會呼叫loadData方法,根據不同的獲取資源策略載入資料,在HttpUrlFetcher類裡也就是網路請求資料的loadData方法中,會請求url拿到輸入流,然後回撥給Generator,Generator的onDataReady方法接收到回撥之後會根據快取策略選擇將資料快取起來或是回撥資料給外部。
public boolean startNext() { //更新快取 if (dataToCache != null) { Object data = dataToCache; dataToCache = null; cacheData(data); } if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) { return true; } sourceCacheGenerator = null; loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++); if (loadData != null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; //載入資料 loadData.fetcher.loadData(helper.getPriority(), this); } } return started; } //HttpUrlFetcher.java public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) { long startTime = LogTime.getLogTime(); try { //獲得輸入流 InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); //回撥 callback.onDataReady(result); } catch (IOException e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Failed to load data for url", e); } callback.onLoadFailed(e); } finally { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)); } } } public void onDataReady(Object data) { DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy(); //快取策略是可以快取資料的話就快取資料 if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { dataToCache = data; // We might be being called back on someone else's thread. Before doing anything, we should // reschedule to get back onto Glide's thread. cb.reschedule(); } else { //不能快取就仍舊向外回撥資料 cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); } } 複製程式碼
2.3.2 onLoadStarted
當status的狀態為running或者WAITING_FOR_SIZE的時候,就會呼叫該方法,它會呼叫target的onLoadStarted做一些準備工作,在ImageViewTarget
類中就會設定placeholder和一些載入動畫。
public void onLoadStarted(@Nullable Drawable placeholder) { super.onLoadStarted(placeholder); setResourceInternal(null); setDrawable(placeholder); } 複製程式碼
2.3.3 onResourceReady
這個方法就是最後將獲得資料裝進imageview或者返回給target的方法:
private synchronized 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; if (glideContext.getLogLevel() <= Log.DEBUG) { Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from " + dataSource + " for " + model + " with size [" + width + "x" + height + "] in " + LogTime.getElapsedMillis(startTime) + " ms"); } 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); if (!anyListenerHandledUpdatingTarget) { Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource); // 1 target.onResourceReady(result, animation); } } finally { isCallingCallbacks = false; } notifyLoadSuccess(); } 複製程式碼
重點看一下注釋1出的target.onResourceReady,它將獲取到的資料通過target呼叫塞入target中進行載入,看一下ImageViewTarget的方法:
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) { //沒有變化 則直接setresource 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 void setResource(@Nullable Drawable resource) { view.setImageDrawable(resource); } 複製程式碼