Android體系課-開源框架-這是一份詳細的Glide原始碼分析文章

語言: CN / TW / HK

前言

最近在元件化開發中準備封裝一個圖片載入庫,於是乎就有了這篇文章

本篇文章對Glide原始碼過程做了一個詳細的講解,也是為了記錄下自己對Glide的理解,以後忘記還可以從這裡查詢。

這裡我有幾點建議: - 看原始碼前先問下自己: 你為什麼去看原始碼,是為了面試?學習?或者和我一樣為了封裝一個自己的圖片載入庫? 如果是為了面試,建議先列出幾種同類型的開源庫,比如我們的圖片載入庫有Picasso,Fresco和Glide,首先你要知道他們的基本使用方式,能說出他們的優缺點,並從中挑選一個類庫去深入瞭解,因為面試官很大可能會讓你自己設計一套類似的開源庫,那這個時候你有對某個類庫做過深入瞭解,會起到事半功倍的效果,就算不能完整設計出來,也可以說出一些基本原理和大致的架構。

個人對看原始碼的思路:

先看整體框架:然後找到一個切入點,深入框架內部去學習。學習中間記得根據框架進行分階段總結,這樣才能不會深陷程式碼泥潭。好了下面我們開始吧。。

整體框架圖

glide整體框架圖.awebp

原始碼梳理

我們將原始碼分為三個部分來講解: with loadinto

這個三個部分就是我們看原始碼的切入點:

步驟1:with:

Glide.java

```java public static RequestManager with(@NonNull Context context) { return getRetriever(context).get(context); }

private static RequestManagerRetriever getRetriever(@Nullable Context context) { return Glide.get(context).getRequestManagerRetriever(); }

public static Glide get(@NonNull Context context) { checkAndInitializeGlide(context); } private static void checkAndInitializeGlide(@NonNull Context context) { initializeGlide(context); }

private static void initializeGlide(@NonNull Context context) { initializeGlide(context, new GlideBuilder()); } private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) { Context applicationContext = context.getApplicationContext(); //1.獲取註解自動生成的AppGlideModule GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(); List manifestModules = Collections.emptyList(); if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) { //這裡解析manifest中的,metedata欄位,並新增到list的格式的manifestModules中 manifestModules = new ManifestParser(applicationContext).parse(); } ... RequestManagerRetriever.RequestManagerFactory factory = annotationGeneratedModule != null ? annotationGeneratedModule.getRequestManagerFactory() : null; //給builder設定一個RequestManagerFactory,用於建立RequestManager builder.setRequestManagerFactory(factory); for (com.bumptech.glide.module.GlideModule module : manifestModules) { module.applyOptions(applicationContext, builder); } if (annotationGeneratedModule != null) { annotationGeneratedModule.applyOptions(applicationContext, builder); } //建立Glide物件 --關注點1-- Glide glide = builder.build(applicationContext); //這裡給manifestModules中設定的GlideModule註冊到應用的生命週期中 for (com.bumptech.glide.module.GlideModule module : manifestModules) { module.registerComponents(applicationContext, glide, glide.registry); } //將應用的applicationContext和build建立的glide註冊到: //之前使用註解@GlideModule建立的GlideApp中,可以讓GlideApp在退出時,可以對glide或者app做一些處理 if (annotationGeneratedModule != null) { annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry); } //這裡將glide註冊到應用的生命週期中: //glide實現了ComponentCallbacks介面,這個介面有兩個方法onConfigurationChanged和onLowMemory, //在應用回撥這兩個介面的時候,glide可以感知到,可以對快取或者圖片做一些處理等 applicationContext.registerComponentCallbacks(glide); Glide.glide = glide; }

```

--關注點1--

java Glide build(@NonNull Context context) { //建立SourceExecutor執行緒池 if (sourceExecutor == null) { sourceExecutor = GlideExecutor.newSourceExecutor(); } //建立diskCacheExecutor磁碟快取執行緒池 if (diskCacheExecutor == null) { diskCacheExecutor = GlideExecutor.newDiskCacheExecutor(); } //建立animationExecutor動畫執行緒池 if (animationExecutor == null) { animationExecutor = GlideExecutor.newAnimationExecutor(); } //建立記憶體大小測量器,後期會根據這個測量器測出的記憶體狀態,對快取進行操作 if (memorySizeCalculator == null) { memorySizeCalculator = new MemorySizeCalculator.Builder(context).build(); } //這個類裡面對Activity或者Fragment的生命週期做了監聽,在Activity或者Fragment處於活躍狀態時去監聽網路連線狀態,在監聽中做一些重啟等操作 if (connectivityMonitorFactory == null) { connectivityMonitorFactory = new DefaultConnectivityMonitorFactory(); } //建立一個BitMap的快取池,內部FIFO if (bitmapPool == null) { int size = memorySizeCalculator.getBitmapPoolSize(); if (size > 0) { bitmapPool = new LruBitmapPool(size); } else { bitmapPool = new BitmapPoolAdapter(); } } //建立一個快取池列表 if (arrayPool == null) { arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes()); } //建立一個Resource快取池 if (memoryCache == null) { memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize()); } //建立磁碟快取池 if (diskCacheFactory == null) { diskCacheFactory = new InternalCacheDiskCacheFactory(context); } //將上面建立的所有物件封裝到一個Engine物件中,以後所有需要的緩衝池或者執行緒池都在這裡面獲取即可 if (engine == null) { engine = new Engine( memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor, GlideExecutor.newUnlimitedSourceExecutor(), animationExecutor, isActiveResourceRetentionAllowed); } //這裡是回撥列表,使用者設定的Listener有可能會有多個 if (defaultRequestListeners == null) { defaultRequestListeners = Collections.emptyList(); } else { defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners); } //這裡是設定一些Glide的實驗性資料,這裡不用太過關注 GlideExperiments experiments = glideExperimentsBuilder.build(); RequestManagerRetriever requestManagerRetriever = new RequestManagerRetriever(requestManagerFactory, experiments); //最後將上面建立的所有物件都儲存到一個Glide物件中,並返回 return new Glide( context, engine, memoryCache, bitmapPool, arrayPool, requestManagerRetriever, connectivityMonitorFactory, logLevel, defaultRequestOptionsFactory, defaultTransitionOptions, defaultRequestListeners, experiments); }

看Glide構造方法:

```java Glide(...){ 1.建立了一個Registry,這個方法主要是用來註冊modelClass和dataClass和factory對應,由此可以通過modelClass找到對應的dataClass和factory registry = new Registry(); registry.register(new DefaultImageHeaderParser());

registry
    .append(ByteBuffer.class, new ByteBufferEncoder())
    .append(InputStream.class, new StreamEncoder(arrayPool))
    ...
    //這裡是一個比較重要的,標註TODO1。後面呼叫DataFetch會用到這個註冊的factory 
    .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
    ..
//這裡省略了很多append呼叫:

} ```

回到呼叫 “關注點1”的程式碼處 initializeGlide方法講解完後,我們回撥最開始的with呼叫方法處:

java public static RequestManager with(@NonNull Context context) { return getRetriever(context).get(context); } 這裡有個get方法:我們進入看看 public RequestManager get(@NonNull Context context) { //傳入的context不能為null 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 // Only unwrap a ContextWrapper if the baseContext has a non-null application context. // Context#createPackageContext may return a Context without an Application instance, // in which case a ContextWrapper may be used to attach one. && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) { return get(((ContextWrapper) context).getBaseContext()); } } return getApplicationManager(context); }

這裡我們看到:

  • 1.如果woth方法在子執行緒執行,則會走到getApplicationManager(context),這裡會建立一個全域性的Glide,不會隨著控制元件生命週期銷燬
  • 2.如果是主執行緒
    • 2.1:context是FragmentActivity,返回一個Fragment的生命週期的Glide RequestManager
    • 2.2:context是Activity,返回一個Activity的生命週期的Glide RequestManager
    • 2.3:context是ContextWrapper,返回一個ContextWrapper的生命週期的Glide RequestManager
  • 3.其他情況就返回應用層的RequestManager

注意

所以在子執行緒中建立的RequestManager都是全域性應用的RequestManager,只能感知應用狀態,無法感知控制元件狀態

總結with方法:

作用:初始化glide:建立了多種執行緒池,多種快取池,將glide註冊到應用控制元件的生命週期中,可以感知應用的記憶體狀態以及介面配置等資訊

返回值:RequestManager

步驟2:load

java public RequestBuilder<Drawable> load(@Nullable String string) { return asDrawable().load(string); } public RequestBuilder<Drawable> asDrawable() { return as(Drawable.class); } public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class<ResourceType> resourceClass) { return new RequestBuilder<>(glide, this, resourceClass, context); }

  • 看到asDrawable其實就是建立了一個RequestBuilder

來看RequestBuilderload方法:

java public RequestBuilder<TranscodeType> load(@Nullable String string) { return loadGeneric(string); } private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { //這個判斷預設為false if (isAutoCloneEnabled()) { return clone().loadGeneric(model); } this.model = model; isModelSet = true; return selfOrThrowIfLocked(); }

load方法很簡單:

就是建立了一個RequestBuilder,並設定了請求的url資訊

步驟3:into

這裡才是glide的重點

前面兩個步驟都是初始化操作 我們將into方法分為三個階段: - decodeJob前 - decodeJob開始 - decodeJob結束

階段1:decodeJob前

```java public ViewTarget into(@NonNull ImageView view) { //先斷言是在主執行緒上,如果是子執行緒則拋異常退出, //這就話可以看出glide可以在子執行緒初始化,但是into操作一定需要在主執行緒執行 Util.assertMainThread(); //傳入的view不能為null,否則會拋異常 Preconditions.checkNotNull(view);

BaseRequestOptions<?> requestOptions = this;
//這裡是設定view的scaleType,glide預設提供了四種scaleType
if (!requestOptions.isTransformationSet()
    && requestOptions.isTransformationAllowed()
    && view.getScaleType() != null) {
  // Clone in this method so that if we use this RequestBuilder to load into a View and then
  // into a different target, we don't retain the transformation applied based on the previous
  // View's scale type.
  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(
    glideContext.buildImageViewTarget(view, transcodeClass), //·關注點2·
    /*targetListener=*/ null,
    requestOptions,
    Executors.mainThreadExecutor());

} ```

來看·關注點2·

java 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)"); } } }

可以看到 - 傳入的是Bitmap.class.則建立BitmapImageViewTarget - 如果是Drawable.class,則建立DrawableImageViewTarget

並將傳入的ImageView包裹進

步驟2 load方法中有了解過,load方法預設呼叫了asDrawable,所以返回的是Drawable型別的資料, 這裡如果需要拿到的是Bitmap的型別圖片資料,則需要呼叫asBitmap,跳過預設asDrawable方法的執行

繼續回到關注點2呼叫的地方:呼叫了一個內部的into方法

```java private > Y into( @NonNull Y target, @Nullable RequestListener targetListener, BaseRequestOptions<?> options, Executor callbackExecutor) { ... //關注點3 Request request = buildRequest(target, targetListener, options, callbackExecutor);

Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
    && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
  // 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;

} ```

來看關注點3

java private Request buildRequest( Target<TranscodeType> target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> requestOptions, Executor callbackExecutor) { return buildRequestRecursive( /*requestLock=*/ new Object(), target, targetListener, /*parentCoordinator=*/ null, transitionOptions, requestOptions.getPriority(), requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight(), requestOptions, callbackExecutor); }

繼續看buildRequestRecursive 為了不讓大家看的頭暈,非關鍵程式碼我這邊省略了:

```java private Request buildRequestRecursive(...) {

...
//這裡建立一個縮圖的請求:關注點4
Request mainRequest =
    buildThumbnailRequestRecursive(
        requestLock,
        target,
        targetListener,
        parentCoordinator,
        transitionOptions,
        priority,
        overrideWidth,
        overrideHeight,
        requestOptions,
        callbackExecutor);
...
//這裡建立一個errorRequest請求
Request errorRequest =
    errorBuilder.buildRequestRecursive(
        requestLock,
        target,
        targetListener,
        errorRequestCoordinator,
        errorBuilder.transitionOptions,
        errorBuilder.getPriority(),
        errorOverrideWidth,
        errorOverrideHeight,
        errorBuilder,
        callbackExecutor);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;

} ```

進入關注點4

```java private Request buildThumbnailRequestRecursive(...) { ... if (thumbnailBuilder != null) { Request fullRequest = obtainRequest(...) ... Request thumbRequest = thumbnailBuilder.buildRequestRecursive(...) coordinator.setRequests(fullRequest, thumbRequest); return coordinator; }else if(thumbSizeMultiplier != null){ Request fullRequest = obtainRequest(...) Request thumbnailRequest = obtainRequest() coordinator.setRequests(fullRequest, thumbnailRequest); return coordinator; }else{ return obtainRequest( requestLock, target, targetListener, requestOptions, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); }

}

進入obtainRequest看看:

private Request obtainRequest( Object requestLock, Target target, RequestListener targetListener, BaseRequestOptions<?> requestOptions, RequestCoordinator requestCoordinator, TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, Executor callbackExecutor) { return SingleRequest.obtain( context, glideContext, requestLock, model, transcodeClass, requestOptions, overrideWidth, overrideHeight, priority, target, targetListener, requestListeners, requestCoordinator, glideContext.getEngine(), transitionOptions.getTransitionFactory(), callbackExecutor); } } `` - 這裡建立了一個SingleRequest`的請求類,先記住這裡

總結下關注點3:buildRequest

如果有要求載入縮圖請求的,會將縮圖請求和圖片真實請求放到一起。 如果沒有縮圖請求,則建立一個SingleRequest的請求返回給呼叫測

我們回撥關注點3被呼叫處:這裡我將前面程式碼再次拷貝一次大家就不用再次向前翻找了。

```java private > Y into( @NonNull Y target, @Nullable RequestListener targetListener, BaseRequestOptions<?> options, Executor callbackExecutor) { ... //關注點3 Request request = buildRequest(target, targetListener, options, callbackExecutor);

Request previous = target.getRequest();
//這裡判斷當前建立的請求和前面的請求是否是同一個請求,我們看不同的情況,所以跳過這裡
if (request.isEquivalentTo(previous)
    && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
  // 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;
}
//請求前先呼叫clear一下這個target
requestManager.clear(target);
target.setRequest(request);
//這裡是真實請求入口 我們進去看看
requestManager.track(target, request);

return target;

} track方法: synchronized void track(@NonNull Target<?> target, @NonNull Request request) { //這裡將target放入一個targets集合中 targetTracker.track(target); //請求入口 requestTracker.runRequest(request); } runRequest方法: public void runRequest(@NonNull Request request) { requests.add(request); //如果不是暫停狀態,則呼叫begin啟動請求,如果是,則將request放入pendingRequests,延遲啟動, if (!isPaused) { request.begin(); } else { request.clear(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Paused, delaying request"); } pendingRequests.add(request); } }

```

進入begin

在講解SingleRequest前,我們下來了解下一個Request的幾種狀態:

java private enum Status { /** Created but not yet running. */ PENDING, /** In the process of fetching media. */ RUNNING, /** Waiting for a callback given to the Target to be called to determine target dimensions. */ WAITING_FOR_SIZE, /** Finished loading media successfully. */ COMPLETE, /** Failed to load media, may be restarted. */ FAILED, /** Cleared by the user with a placeholder set, may be restarted. */ CLEARED, }

這個StatusSingleRequest的一個內部列舉類: - PENDING:掛起狀態,還未執行,待執行,前面我們說過如果Request處於isPaused狀態,會新增進入pendingRequests的list中,這個裡面的Request就是處於PENDING狀態 - RUNNING:這個狀態的Request正在去伺服器拿資料階段 - WAITING_FOR_SIZE:等待獲取ImageView的一個尺寸階段 - COMPLETE:成功拿到請求資料 - FAILED:沒有獲取到請求資料,在一定情況下會重啟,例如:在網路狀態良好的時候,Glide會自動重啟請求,去獲取資料,因為Glide註冊了網路狀態的監聽 - CLEARED:被使用者清理狀態,使用一個placeholder代替資料元,在一定情況下會重啟Request

好了,有了上面的基礎我們再來看下面程式碼:

這個request是在關注點3處建立的SingleRequest

```java public void begin() { synchronized (requestLock) { ... //請求處於RUNNING狀態,這個異常會被丟棄 if (status == Status.RUNNING) { throw new IllegalArgumentException("Cannot restart a running request"); } ... //請求處於COMPLETE,則回撥onResourceReady,讓應用去記憶體中獲取,這裡的DataSource.MEMORY_CACHE:標誌資料元在記憶體快取中 if (status == Status.COMPLETE) { onResourceReady( resource, DataSource.MEMORY_CACHE, / isLoadedFromAlternateCacheKey= / false); return; } ...

  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());
  }

}

} ```

begin方法中,看到對請求的狀態做了判斷,只有是未處理的請求才會去請求資料,其他情況根據具體狀態處理

我們進入onSizeReady看看:

```java public void onSizeReady(int width, int height) { ... loadStatus = engine.load(...) ... } public LoadStatus load(...){ EngineResource<?> memoryResource; synchronized (this) { //去快取池中獲取資料 ,這裡面內部先去活動的快取中activeResources獲取圖片資料,如果沒有獲取到就去快取池cache中獲取,都是通過對應的key獲取 memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); //沒有取到快取資料,則進入waitForExistingOrStartNewJob,看名字應該是去請求資料的方法,我們進入這裡面看看 if (memoryResource == null) { return waitForExistingOrStartNewJob( glideContext, model, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, options, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache, cb, callbackExecutor, key, startTime); } }

// Avoid calling back while holding the engine lock, doing so makes it easier for callers to
// deadlock.
cb.onResourceReady(
    memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return null;

}

private LoadStatus waitForExistingOrStartNewJob( ... //這裡建立一個EngineJob EngineJob engineJob = engineJobFactory.build( key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache); //這裡建立一個decodeJob DecodeJob decodeJob = decodeJobFactory.build( glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob); //將engineJob放入jobs中 jobs.put(key, engineJob); //給engineJob添加回調和執行緒池執行器 engineJob.addCallback(cb, callbackExecutor); //啟動engineJob,並傳入decodeJob engineJob.start(decodeJob);

if (VERBOSE_IS_LOGGABLE) {
  logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob)

} ```

進入engineJob.start(decodeJob)方法看看:

java public synchronized void start(DecodeJob<R> decodeJob) { this.decodeJob = decodeJob; GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); }

  • 內部其實就是使用執行緒池去請求decodeJob

  • 執行decodeJob前,我們先來梳理下我們階段1分析的流程

```java RequestBuilder.java into{ 1.對ScaleType的RequestOption做處理 2.呼叫內部into方法執行 into(引數1:DrawableImageViewTarget(view),引數2:requestOptions,引數3:主執行緒執行器); {
1.建立Request:buildRequest,返回值:SingleRequest物件 2.檢查是否和前一個請求是同一個,如果是,則直接使用前一個請求傳送begin方法,否就走第3步 3.清除當前target請求; 4.進入requestManager.track(target, request): track{ 1.呼叫requestTracker.runRequest(request): runRequest{ //如果不是暫停狀態,則呼叫begin啟動請求,如果是,則將request放入pendingRequests,延遲啟動, 1.request.begin():這個request是一個SingleRequest物件 begin{ 1.對請求狀態做判斷 2.呼叫onSizeReady onSizeReady{ engine.load{ 1.去快取中獲取資料:loadFromMemory 2.1中沒有獲取到快取資料,則呼叫waitForExistingOrStartNewJob waitForExistingOrStartNewJob{ 1.這裡面啟動//啟動engineJob,並傳入decodeJob:engineJob.start(decodeJob) start{ 1.呼叫執行緒池啟動decodeJob } }

                    }
                }
            }           
          }       
      }     
}

} ```

階段2:decodeJob執行階段

在講解decodeJob執行階段前我們先來了解下Job的幾種狀態:

java /** Why we're being executed again. */ private enum RunReason { /** The first time we've been submitted. */ INITIALIZE, /** We want to switch from the disk cache service to the source executor. */ SWITCH_TO_SOURCE_SERVICE, /** * We retrieved some data on a thread we don't own and want to switch back to our thread to * process the data. */ DECODE_DATA, }

這個RunReason看名字就知道:這裡是表示你啟動這個Job是要做什麼的,主要有三種狀態:

  • INITIALIZE: 就是一個新的請求必須經過的第一步,這裡job主要任務是去快取中取資料

  • SWITCH_TO_SOURCE_SERVICE: 就是我們希望這個job實現從磁碟服務到資源獲取階段, 這個怎麼理解呢,瞭解OkHttp原始碼都知道,OkHttp內部使用的是攔截器的模式對請求做處理,每個請求都需要經過攔截器依次處理,最後得到請求資料, 如果OkHttp發現快取攔截器中有資料,則返回,沒有資料,則執行下一個攔截器。 這裡也是一樣,每個job類似一個攔截器,每次都是優先去快取中獲取資料,也就是第一步的INITIALIZE狀態的job,如果沒有資料才會去網路中請求, SWITCH_TO_SOURCE_SERVICE就表示我們快取中沒有獲取到資料,需要啟動下一個job去網路中拿資料。

  • DECODE_DATA: 這個很好理解,就是在非同步執行緒上獲取到資料,希望跳轉到我們自己的執行緒上去解碼資料

這裡再介紹一個內部列舉類:

java /** Where we're trying to decode data from. */ 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, }

  • 這個類標識我們應該去哪個類中執行當前Job: 每個Job都有對應的執行器,執行完後,呼叫startNext執行下一個Job,下一個job又是對應另外一個stage的執行器

主要是在下面一個方法中使用:

java 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); } }

  • RESOURCE_CACHE:對應ResourceCacheGenerator執行器
  • DATA_CACHE:對應DataCacheGenerator執行器
  • SOURCE:對應SourceGenerator執行器

經過上面的分析,我們已經知道:

你可以把一個Job理解為一個攔截器,每次都會根據當前Job的狀態是要不同的執行器去執行任務 - 任務匯流排

1.ResourceCacheGenerator:快取中獲取資料,這個資料是被修改過的降取樣快取

2.DataCacheGenerator:也是快取中獲取資料,這個資料是沒有被修改的網路請求元資料

3.SourceGenerator:這個執行器執行的任務才真正是用於網路請求資料

是不是和我們的OkHttp很像?

有了上面的基礎我們再來對decodeJob原始碼進行分析:

DecodeJob繼承了Runnable介面,我們來看他的run方法

java public void run() { ... runWrapped(); ... } private void runWrapped() { switch (runReason) { case INITIALIZE: //關注點1 stage = getNextStage(Stage.INITIALIZE); //關注點2 currentGenerator = getNextGenerator(); //關注點3 runGenerators(); break; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break; case DECODE_DATA: decodeFromRetrievedData(); break; default: throw new IllegalStateException("Unrecognized run reason: " + runReason); } }

runWrapped方法中對runReason做一個判斷:分別指向不同的請求:

//關注點1 我們看INITIALIZE,呼叫了getNextStage傳入Stage.INITIALIZE

java 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); } }

這裡面是根據傳入的diskCacheStrategy磁碟快取策略對應不同的狀態 我們假設diskCacheStrategy.decodeCachedResource()返回都是truenext關係如下:

java INITIALIZE->RESOURCE_CACHE->DATA_CACHE->SOURCE->FINISHED

回到前面runWrapped方法:

//關注點2 getNextGenerator獲取執行器前面已經講過,我們再發下對應關係:

  • RESOURCE_CACHE:對應ResourceCacheGenerator執行器
  • DATA_CACHE:對應DataCacheGenerator執行器
  • SOURCE:對應SourceGenerator執行器

//關注點3 呼叫runGenerators開始執行對應的job

```java 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;
  }
}

} ```

可以看到這裡有個while迴圈就是根據當前stage狀態迴圈執行不同的請求:

按前面我們的分析

優先會執行: ResourceCacheGenerator ,然後DataCacheGenerator,最後SourceGenerator

篇幅問題:我們這隻具體分析SourceGenerator的方法:其他兩個執行器前面已經介紹過

```java SourceGenerator.java public boolean startNext() { ... 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; startNextLoad(loadData); } } ... return started; }

看startNextLoad方法: private void startNextLoad(final LoadData<?> toStart) { loadData.fetcher.loadData( helper.getPriority(), new DataCallback() { @Override public void onDataReady(@Nullable Object data) { if (isCurrentRequest(toStart)) { onDataReadyInternal(toStart, data); } }

      @Override
      public void onLoadFailed(@NonNull Exception e) {
        if (isCurrentRequest(toStart)) {
          onLoadFailedInternal(toStart, e);
        }
      }
    });

} ```

這個fetcher = MultiFetcher物件 進入MultiFetcher的loadData

java public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) { ... fetchers.get(currentIndex).loadData(priority, this); ... }

fetchers是一個List,內部儲存的是一個HttpUrlFeacher 這裡傳入了一個this作為callback回撥,後面會用到:this = MultiFetcher

```java HttpUrlFeacher.java

public void loadData( @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) { ... try { InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); callback.onDataReady(result); } catch (IOException e) {
callback.onLoadFailed(e);//異常回調onLoadFailed介面給應用層 } finally { ... } } loadDataWithRedirects方法: private InputStream loadDataWithRedirects( URL url, int redirects, URL lastUrl, Map headers) throws HttpException { ... //關注點1 urlConnection = buildAndConfigureConnection(url, headers);

try {
  // Connect explicitly to avoid errors in decoders if connection fails.
  urlConnection.connect();
  // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
  stream = urlConnection.getInputStream();
} catch (IOException e) {
  throw new HttpException(
      "Failed to connect or obtain data", getHttpStatusCodeOrInvalid(urlConnection), e);
}
...
//這裡獲取返回code
final int statusCode = getHttpStatusCodeOrInvalid(urlConnection);
if (isHttpOk(statusCode)) {
    //關注點2
  return getStreamForSuccessfulRequest(urlConnection);
} else if (isHttpRedirect(statusCode)) {
  String redirectUrlString = urlConnection.getHeaderField(REDIRECT_HEADER_FIELD);
  if (TextUtils.isEmpty(redirectUrlString)) {
    throw new HttpException("Received empty or null redirect url", statusCode);
  }
  URL redirectUrl;
  try {
    redirectUrl = new URL(url, redirectUrlString);
  } catch (MalformedURLException e) {
    throw new HttpException("Bad redirect url: " + redirectUrlString, statusCode, e);
  }
  // Closing the stream specifically is required to avoid leaking ResponseBodys in addition
  // to disconnecting the url connection below. See #2352.
  cleanup();
  return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == INVALID_STATUS_CODE) {
  throw new HttpException(statusCode);
} else {
  try {
    throw new HttpException(urlConnection.getResponseMessage(), statusCode);
  } catch (IOException e) {
    throw new HttpException("Failed to get a response message", statusCode, e);
  }
}

}

```

//關注點1 這裡主要是建立HttpUrlConnection請求

```java private HttpURLConnection buildAndConfigureConnection(URL url, Map headers) throws HttpException { HttpURLConnection urlConnection; try { urlConnection = connectionFactory.build(url); } catch (IOException e) { throw new HttpException("URL.openConnection threw", /statusCode=/ 0, e); } for (Map.Entry headerEntry : headers.entrySet()) { urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue()); } urlConnection.setConnectTimeout(timeout); urlConnection.setReadTimeout(timeout); urlConnection.setUseCaches(false); urlConnection.setDoInput(true); // Stop the urlConnection instance of HttpUrlConnection from following redirects so that // redirects will be handled by recursive calls to this method, loadDataWithRedirects. urlConnection.setInstanceFollowRedirects(false); return urlConnection; }

```

//關注點2 返回成功,則獲取InputStream

```java private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection) throws HttpException { try { if (TextUtils.isEmpty(urlConnection.getContentEncoding())) { int contentLength = urlConnection.getContentLength(); stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength); } else { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding()); } stream = urlConnection.getInputStream(); } } catch (IOException e) { throw new HttpException( "Failed to obtain InputStream", getHttpStatusCodeOrInvalid(urlConnection), e); } return stream; }

繼續回到HttpUrlFeacher的loadData方法 public void loadData( @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) { ... try { InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); callback.onDataReady(result); } catch (IOException e) {
callback.onLoadFailed(e);//異常回調onLoadFailed介面給應用層 } finally { ... } } ```

把返回的InputStream通過callback回撥給上一級 這個callback 前面分析過:callBack = MultiFetcher 回到MultiFetcher的onDataReady方法中:

java public void onDataReady(@Nullable Data data) { if (data != null) { callback.onDataReady(data); } else { startNextOrFail(); } }

看到這裡又呼叫了一次callback.onDataReady(data); 這個callback = loadData傳入下來的SourceGenerator中的startNextLoad方法:

```java SourceGenerator.java private void startNextLoad(final LoadData<?> toStart) { loadData.fetcher.loadData( helper.getPriority(), new DataCallback() { @Override public void onDataReady(@Nullable Object data) { if (isCurrentRequest(toStart)) { onDataReadyInternal(toStart, data); } }

      @Override
      public void onLoadFailed(@NonNull Exception e) {
        if (isCurrentRequest(toStart)) {
          onLoadFailedInternal(toStart, e);
        }
      }
    });

} ```

回撥這裡再次呼叫onDataReadyInternal方法:

java void onDataReadyInternal(LoadData<?> loadData, 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); } }

看cb.reschedule()方法:這個cb = DecodeJob

java DecodeJob.java public void reschedule() { runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; callback.reschedule(this); } 這個callback是EngineJob EngineJob.java public void reschedule(DecodeJob<?> job) { // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself // up. getActiveSourceExecutor().execute(job); }

到這裡又去執行了一次job,這個job 的 runReason = RunReason.SWITCH_TO_SOURCE_SERVICE 前面第一次獲取網路資料也呼叫過這個: 前面是為了獲取快取資料:這次是為了將資料寫入快取

最後將資料通過DecodeJob的onDataFetcherReady回調出去

這裡對階段2做一個總結: - 工作:主要負責去快取中取資料,如果沒有取到就去網路伺服器拉資料,獲取到資料後,將資料放入到快取中。 使用的是Job模式,每個Job都有一個狀態,每個狀態對於一個執行器,類似OkHttp中的攔截器模式

階段3解碼資料

```java 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; this.isLoadingFromAlternateCacheKey = sourceKey != decodeHelper.getCacheKeys().get(0);

if (Thread.currentThread() != currentThread) {
  runReason = RunReason.DECODE_DATA;
  callback.reschedule(this);
} else {
  GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
  try {
    decodeFromRetrievedData();
  } finally {
    GlideTrace.endSection();
  }
}

} ```

  • 這個方法中又呼叫了decodeFromRetrievedData
  • 作用:解碼得到的響應資料

java private void decodeFromRetrievedData() { try { //這裡解碼資料 關注點1 resource = decodeFromData(currentFetcher, currentData, currentDataSource); } catch (GlideException e) { e.setLoggingDetails(currentAttemptingKey, currentDataSource); throwables.add(e); } if (resource != null) { //通知應用 關注點2 notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey); } else { runGenerators(); } }

關注點1

java decodeFromData decodeFromFetcher(data, dataSource); runLoadPath(data, dataSource, path); path.load(rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource)); loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables); result = path.decode(rewinder, width, height, options, decodeCallback); decodeResource(rewinder, width, height, options); decodeResourceWithList(rewinder, width, height, options, exceptions); result = decoder.decode(data, width, height, options); downsampler.decode(source, width, height, options); decode(...); decodeFromWrappedStreams Bitmap downsampled = decodeStream(imageReader, options, callbacks, bitmapPool); result = imageReader.decodeBitmap(options); BitmapFactory.decodeStream(stream(), /* outPadding= */ null, options);

可以看到請求資料解碼過程最後也是通過BitmapFactory.decodeStream將資料轉換為Bitmap返回 但是這個過程中Glide對資料做了多重處理,包括裁剪,降採樣等操作

關注點2:notifyEncodeAndRelease

```java private void notifyEncodeAndRelease( ...核心程式碼 notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey); } private void notifyComplete( Resource resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) { setNotifiedOrThrow(); callback.onResourceReady(resource, dataSource, isLoadedFromAlternateCacheKey); } 呼叫了callback.onResourceReady: 這個callback = EngineJob EngineJob.java public void onResourceReady( Resource resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) { synchronized (this) { this.resource = resource; this.dataSource = dataSource; this.isLoadedFromAlternateCacheKey = isLoadedFromAlternateCacheKey; } notifyCallbacksOfResult(); } void notifyCallbacksOfResult() { ... for (final ResourceCallbackAndExecutor entry : copy) { entry.executor.execute(new CallResourceReady(entry.cb)); } decrementPendingCallbacks(); }

執行了CallResourceReady的run方法 run callCallbackOnResourceReady(cb); cb.onResourceReady(engineResource, dataSource, isLoadedFromAlternateCacheKey); cb = SingleRequest onResourceReady target.onResourceReady(result, animation); 這個target = BitmapImageViewTarget ,這裡呼叫其父類ImageViewTarget的onResourceReady方法 setResourceInternal(resource); setResource(resource);執行BitmapImageViewTarget的setResource view.setImageBitmap(resource);到這裡就執行完畢了 ```

  • 總結階段3:就是對網路資料根據需求進行降取樣,並將資料設定到對應的target中

這裡對整個步驟3:into方法做一個總結吧:

階段1: 建立對應的網路請求,對比當前請求和記憶體中前幾個請求是否一致,如果一直,使用前一個請求進行網路請求。 去快取中獲取對應的資料,如果沒有獲取到就去網路拉取資料

階段2: 主要負責去快取中取資料,如果沒有取到就去網路伺服器拉資料,獲取到資料後,將資料放入到快取中。 使用的是Job模式,每個Job都有一個狀態,每個狀態對於一個執行器,類似OkHttp中的攔截器模式 根據job狀態的切換,執行各自的請求

階段3:對資料進行解碼,解碼會進行根據需求進行降取樣,並將獲取的圖片資料傳遞給對應的Target

Glide快取

這裡我們對Glide三級快取做一個總結:因為一些面試還是經常會問到的

1.怎麼快取? 如果支援磁碟快取,則將原Source快取到磁碟,在經過解碼處理後,將解碼後的資料快取到activeResourcesactiveResources是一個弱引用的物件,在記憶體回收的時候,會將資料放到cache快取下面

2.怎麼使用快取

如果直接記憶體快取,則去activeResources中取解碼後的資料,如果activeResources中沒有對應的快取,則去cache快取中獲取,如找到則直接返回cache資料,並將快取資料寫入activeResources中,刪除cache中的快取資料, 如果還沒有,則在呼叫網路請求前,去磁碟中獲取,磁碟中獲取的資料是圖片原資料,需要經過解碼處理才能被控制元件使用,磁碟如果還沒有,才會去網路中請求資料。

以上就是Glide對三級快取的使用機制。

總結

最後用一幅圖來整理下過程:來自 JsonChao

因為是舊版本畫的流程圖,所以有些地方可能會不一致,但是大體流程是一致的,可以參考圖片再去分析原始碼

glide原始碼分析流程.awebp