眨眼之間畢業已經3年,可是本身的技術成長視乎沒有跟上時間的步伐,趁着51假期,決定來分析學習一下Glide圖片加載框架。 文章打算從下面幾個方面來學習分析Glide。html
glide是咱們項目開發中很是常見的一個三方框架,它的功能極其強大它爲咱們出來好了圖片的加載緩存等功能。使用起來很是方便。那麼咱們應該如何來進行使用呢?它的實現原來又是什麼呢?java
Glide地址: github.com/bumptech/gl…git
Glide官方使用文檔: muyangmin.github.io/glide-docs-…github
文章glide的使用版本:4.9.0緩存
本篇文章主要對glide的加載流程進行分析。bash
簡單的加載markdown
//context可使activity,application,fragment //imageUrl爲圖片地址 //image 須要顯示的圖片 Glide.with(context).load(imageUrl).into(image); 複製代碼
設置佔位圖以及加載出錯的圖片網絡
Glide.with(context)
.load(imageUrl)
.placeholder(R.drawable.coordinator_demo)
.error(R.drawable.coordinator_demo)
.into(image);
複製代碼
上面是咱們爲單個圖片的加載設置配置,可是常見的的狀況下咱們許多圖片的加載除了加載地址和須要顯示的ImageView不一樣其餘的都是相同的。難道咱們要爲每個圖片的加載都進行設置嗎?答案是否認的,glide爲咱們提供了一個對象:RequestOptions方便咱們共享不一樣模塊的圖片加載項。app
val requestOptions = RequestOptions() requestOptions .placeholder(R.drawable.coordinator_demo) .error(R.drawable.coordinator_demo) Glide.with(context).load(imageUrl).apply(requestOptions).into(image); 複製代碼
RequestOptions對象的建立很是簡單,能夠直接new一個對象或者經過它的任意一個靜態方法的調用返回RequestOptions對象。框架
經過給glide的使用咱們發現Glide的圖片加載主要使用了三個方法:
能夠看到拋開廢棄的方法Glide有5個重載的with.它們的返回對象都是RequestManager對象的實例。
經過閱讀源碼咱們發現最終返回的RequestManager對象是由RequestManagerRetriever的get方法返回的。get()有5個重載方法分別對應Glide的5個重載。咱們來一個一個進行分析。
傳遞的Context是Application的context.最終調用的是getApplicationManager方法。其實現以下:
@NonNull 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; } 複製代碼
這裏經過雙重鎖的單例來保證針對Application的Context只有一個RequestManager.
傳入對象是FragmentActivity
@NonNull 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)); } } 複製代碼
能夠看到上面的代碼最終會調用到supportFragmentGet方法,這裏纔是真正返回RequestManager的位置。
@NonNull private RequestManager supportFragmentGet( @NonNull Context context, @NonNull FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) { SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint, isParentVisible); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { // TODO(b/27524013): Factor out this Glide.get() call. Glide glide = Glide.get(context); requestManager = factory.build( glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } return requestManager; } 複製代碼
由於這篇文章知識粗略的瞭解Glide的加載咱們先不去深刻分析這個RequestManager是如何生成的。
傳入對象是Fragment
@NonNull public RequestManager get(@NonNull Fragment fragment) { Preconditions.checkNotNull(fragment.getActivity(), "You cannot start a load on a fragment before it is attached or after it is destroyed"); if (Util.isOnBackgroundThread()) { return get(fragment.getActivity().getApplicationContext()); } else { FragmentManager fm = fragment.getChildFragmentManager(); return supportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible()); } } 複製代碼
一樣的這裏也是調用的supportFragmentGet來獲取RequestManager對象。
這裏咱們不須要太過關注細節只須要知道Glide會根據傳入的不一樣的activity或者Fragment來建立對應的requestManager。並與其生命週期進行綁定
Glide的with方法返回的是RequestManager接着咱們看看RequestManager的load方法。
每個load都會默認調用asDrawable方法。咱們來看看它的實現:
public RequestBuilder<Drawable> asDrawable() { return as(Drawable.class); } public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class<ResourceType> resourceClass) { return new RequestBuilder<>(glide, this, resourceClass, context); } 複製代碼
能夠看到最後返回了泛型類爲Drawable的RequestBuilder。在獲取到RequestBuilder對象後調用了它的load方法。
public RequestBuilder<TranscodeType> load(@Nullable String string) { return loadGeneric(string); } 複製代碼
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { this.model = model; isModelSet = true; return this; } 複製代碼
能夠看到最終requestManager返回了一個RequestBuilder實例對象。除了主動調用requestManager的as方法調用load默認生成的都是RequestBuilder實例對象。
RequestManager和RequestBuilder都實現了ModelTypes接口,RequestManager的做用是生成限定泛型的RequestBuilder對象。RequestBuilder的做用是配置請求圖片須要的參數。
RequestBuilder的into有多個重載方法,可是他們最終都調用了以下的方法:該方法接收四個參數
Target :目標對象;
RequestListener:圖片加載監聽
BaseRequestOptions:加載圖片的配置項
Executor:執行加載圖片的線程池
這裏咱們作流程分析,就不對每一個參數的來源作詳細的解釋了。
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> options, Executor callbackExecutor) { Preconditions.checkNotNull(target); 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 the request is completed, beginning again will ensure the result is re-delivered, // triggering RequestListeners and Targets. If the request is failed, beginning again will // restart the request, giving it another chance to complete. If the request is already // running, we can let it continue running without interruption. if (!Preconditions.checkNotNull(previous).isRunning()) { // Use the previous request rather than the new one to allow for optimizations like skipping // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions // that are done in the individual Request. previous.begin(); } return target; } requestManager.clear(target); target.setRequest(request); //發起圖片加載請求 requestManager.track(target, request); return target; } 複製代碼
上面的代碼主要作了兩件事:構建Request對象;發起圖片加載請求。
在忽略縮略圖的狀況下最終會使用obtainRequest方法構建Request對象
private Request obtainRequest( Target<TranscodeType> target, RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> requestOptions, RequestCoordinator requestCoordinator, TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, Executor callbackExecutor) { return SingleRequest.obtain( context, glideContext, model, transcodeClass, requestOptions, overrideWidth, overrideHeight, priority, target, targetListener, requestListeners, requestCoordinator, glideContext.getEngine(), transitionOptions.getTransitionFactory(), callbackExecutor); } 複製代碼
也就是說最終的request是SingleRequest的實例,須要注意的是SingleRequest是一個帶泛型的類。這裏的R同RequestBuilder的泛型是相同的。即RequestBuilder 對應SingleRequest。
網絡請求的發起:requsetManager的track方法:
synchronized void track(@NonNull Target<?> target, @NonNull Request request) { targetTracker.track(target); requestTracker.runRequest(request); } 複製代碼
從Glide中咱們知道每一個requestManager都有與其綁定生命週期的activity或者fragment: 而後RequestManager又將對應請求的生命週期交給RequestTracker來進行處理。
我來來看看RequestTracker的runRequest方法:
/** * Starts tracking the given request. */ 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); } } 複製代碼
能夠看到在這裏若是當前狀態是暫停的那麼將request加入到一個待執行的列表中,等待下次能夠執行的時候進行加載,處於非暫停狀態調用request的begin()直接進行加載。
由前面的知識咱們知道這個request是一個SingleRequest對象。咱們來看看它的begin方法:
@Override 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; } 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()); } if (IS_VERBOSE_LOGGABLE) { logV("finished run method in " + LogTime.getElapsedMillis(startTime)); } } 複製代碼
這個方法有點長,可是邏輯分廠簡單。須要注意的有下面幾點:
咱們來看看onSizeReady的加載的實現:其核心就只有一句:調用engine的load方法進行加載。
咱們來看看engine的load實現:
public synchronized <R> LoadStatus load( GlideContext glideContext, Object model, Key signature, int width, int height, Class<?> resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<?>, Transformation<?>> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor) { long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0; 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); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } //從緩存中進行加載 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); //若是存在異步加載的key說明已經有一樣的加載正在進行中 if (current != null) { current.addCallback(cb, callbackExecutor); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Added to existing load", startTime, key); } return new LoadStatus(cb, current); } //開啓線程進行異步加載 EngineJob<R> engineJob = engineJobFactory.build( key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache); //注意下這個R它是根據transcodeClass來進行決定的,這個值是SingleRequest<R>的R進行限定,從前面咱們 //知道SingleRequest<R>同RequestBuilder<TranscodeType>的TranscodeType相同。 //即它也是一個Drawable對象 DecodeJob<R> decodeJob = decodeJobFactory.build( glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob); jobs.put(key, engineJob); engineJob.addCallback(cb, callbackExecutor); engineJob.start(decodeJob); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); } 複製代碼
從上面的流程能夠看出Glide的圖片加載有三個來源:
由上面的代碼咱們能夠看出,最後engineJob.start(decodeJob)實現圖片加載,decodeJob是一個runnable對象咱們直接去看看它的run方法。它的核心代碼就只有一句:調用runWrapped()。咱們來看看runWrapped的及其相關的實現
private void runWrapped() { //根據runReason獲取狀態 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 Stage getNextStage(Stage current) { switch (current) { case INITIALIZE: return diskCacheStrategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE); case RESOURCE_CACHE: return diskCacheStrategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE); case DATA_CACHE: // Skip loading from source if the user opted to only retrieve the resource from cache. return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE; case SOURCE: case FINISHED: return Stage.FINISHED; default: throw new IllegalArgumentException("Unrecognized stage: " + current); } } 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); } } 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(); } // Otherwise a generator started a new load and we expect to be called back in // onDataFetcherReady. } 複製代碼
上面的代碼邏輯比較複雜,這裏不作詳細的說明,可是它的大概意思就是,在runGenerators方法中經過調用DataFetcherGenerator的startNext()方法先從文件中讀取圖片資源(分別是資源類型和數據來源),若是不存在咱們在從網絡進行加載。再根據官方文檔的介紹glide的緩存分爲4級
由前面的流程分析咱們知道一、2級緩存在Engine類的load中調用,三、4級緩存在DecodeJob中進行調用。這裏咱們不對緩存的調用進行分析。直接進入網絡在在的分析。
在runGenerators()方法中當stage == Stage.SOURCE會退出循環而且調用reschedule()執行DecodeJob的run方法
@Override public void reschedule() { runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; callback.reschedule(this); } 複製代碼
由前面的知識咱們知道這個時候會在runGenerators方法中執行SourceGenerator的startNext()方法。其實現以下
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; } 複製代碼
經過debug咱們發現loadData的fetcher對象在model是圖片網絡地址時是MultiModelLoader的內部類。
在傳入圖片網絡地址的狀況下最終調用的HttpUrlFetcher進行圖片加載。當數據準備完成後回調到SourceGenerator的onDataReady方法,若是不須要進行緩存則直接通知數據準備完成,不然先進行緩存在通知數據準備完成。
@Override 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); } } 複製代碼
最終它們都會調用DecodeJob的onDataFetcherReady()這個方法又會調用decodeFromRetrievedData()而
decodeFromRetrievedData當資源準備好的時候會調用notifyEncodeAndRelease()來通知數據準備完成。
@Override public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) { this.currentSourceKey = sourceKey; this.currentData = data; this.currentFetcher = fetcher; this.currentDataSource = dataSource; this.currentAttemptingKey = attemptedKey; if (Thread.currentThread() != currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this); } else { GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData"); try { decodeFromRetrievedData(); } finally { GlideTrace.endSection(); } } } private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) { if (resource instanceof Initializable) { ((Initializable) resource).initialize(); } Resource<R> result = resource; LockedResource<R> lockedResource = null; if (deferredEncodeManager.hasResourceToEncode()) { lockedResource = LockedResource.obtain(resource); result = lockedResource; } notifyComplete(result, dataSource); stage = Stage.ENCODE; try { if (deferredEncodeManager.hasResourceToEncode()) { deferredEncodeManager.encode(diskCacheProvider, options); } } finally { if (lockedResource != null) { lockedResource.unlock(); } } // Call onEncodeComplete outside the finally block so that it's not called if the encode process // throws. onEncodeComplete(); } 複製代碼
private void notifyComplete(Resource<R> resource, DataSource dataSource) { setNotifiedOrThrow(); callback.onResourceReady(resource, dataSource); } 複製代碼
咱們須要注意notifyComplete方法裏這個callback是建立DecodeJob對象傳入的EngineJob,所以調用的是EngineJob的onResourceReady()。咱們看看他的onResourceReady方法實現
@Override public void onResourceReady(Resource<R> resource, DataSource dataSource) { synchronized (this) { this.resource = resource; this.dataSource = dataSource; } notifyCallbacksOfResult(); } @Synthetic void notifyCallbacksOfResult() { ResourceCallbacksAndExecutors copy; Key localKey; EngineResource<?> localResource; synchronized (this) { stateVerifier.throwIfRecycled(); if (isCancelled) { // TODO: Seems like we might as well put this in the memory cache instead of just recycling // it since we've gotten this far... resource.recycle(); release(); return; } else if (cbs.isEmpty()) { throw new IllegalStateException("Received a resource without any callbacks to notify"); } else if (hasResource) { throw new IllegalStateException("Already have resource"); } engineResource = engineResourceFactory.build(resource, isCacheable); // Hold on to resource for duration of our callbacks below so we don't recycle it in the // middle of notifying if it synchronously released by one of the callbacks. Acquire it under // a lock here so that any newly added callback that executes before the next locked section // below can't recycle the resource before we call the callbacks. hasResource = true; copy = cbs.copy(); incrementPendingCallbacks(copy.size() + 1); localKey = key; localResource = engineResource; } //這裏會將圖片信息加入活動資源, listener.onEngineJobComplete(this, localKey, localResource); //for循環最終執行SingleRequest的onResourceReady方法 for (final ResourceCallbackAndExecutor entry : copy) { entry.executor.execute(new CallResourceReady(entry.cb)); } decrementPendingCallbacks(); } 複製代碼
在忽略其餘條件的狀況下SingleRequest的onResourceReady()最終會執行一句代碼:
target.onResourceReady(result, animation);
複製代碼
咱們假設圖片加載的時候into傳入的是一個ImageView。前面的時候沒有分析這個target是如何進行構建的這裏咱們補充說明一下:
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { ///省略其它代碼 return into( /** 構建target對象 */ glideContext.buildImageViewTarget(view, transcodeClass), /*targetListener=*/ null, requestOptions, Executors.mainThreadExecutor()); } 複製代碼
target對象經過glideContext.buildImageViewTarget(view, transcodeClass)來進行構建:咱們看看GlideContext對應的實現:
@NonNull public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) { return imageViewTargetFactory.buildTarget(imageView, transcodeClass); } 複製代碼
public class ImageViewTargetFactory { @NonNull @SuppressWarnings("unchecked") public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view, @NonNull 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)"); } } } 複製代碼
由上面的代碼咱們知道若是RequestBuilder的transcodeClass是Bitmap那麼target就是BitmapImageViewTarget,若是是Drawable的子類那麼target就是DrawableImageViewTarget。
咱們選擇DrawableImageViewTarget進行查看:因爲DrawableImageViewTarget沒有onResourceReady方法咱們在它的父類ImageViewTarget中進行查看
@Override public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) { if (transition == null || !transition.transition(resource, this)) { setResourceInternal(resource); } else { maybeUpdateAnimatable(resource); } } 複製代碼
DrawableImageViewTarget的實現:能夠看到它的setResource方法給ImageView設置了加載圖片
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); } @Override protected void setResource(@Nullable Drawable resource) { view.setImageDrawable(resource); } } 複製代碼
到這裏glide的圖片加載流程就分析完了。因爲篇幅較長咱們對glide的加載流程進行一個簡單的回顧。
Glide加載時序圖: