讀源碼-Glide源碼解析


本文基於Glide版本:

com.github.bumptech.glide:glide:4.11.0

Glide最常用的一行代碼如下,也概括了Glide的初始化、加載圖片(本地、緩存、網絡圖片)、綁定顯示的流程。本文就從該行代碼開啓Glide的源碼之旅。

Glide.with(this).load(url).into(mainPic);

1-初始化with()

首先是Glide.with()方法,通過該方法主要是通過RequestManagerRetriever獲取一個RequestManager對象。RequestManager是處理圖片加載過程的具體實現類,後面詳細講述。

調用鏈路:(1)Glide.with

–>(2)Glide.getRetriever
–>(3)Glide.get
–>(4)Glide.getRequestManagerRetriever
–>(5)RequestManagerRetriever.get
–>(6)RequestManager

(1)Glide.with

public static RequestManager with(@NonNull FragmentActivity activity) {
    return getRetriever(activity).get(activity);
}

(2)Glide.getRetriever獲取RequestManagerRetriever

private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    //判空檢測,判斷對象空指針以及Activity/Fragment是否處於活躍狀態
    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();
}

(3)Glide.get(context)獲取Glide的全局單例。這麼多邏輯主要是處理用戶自定義AppGlideModule的情況。

  • 繼承AppGlideModule實現自定義Glide,重寫網絡加載框架、緩存路徑、自定義緩存等定製內容,在class前加上@GlideModule註解
  • 通過Annotation Processer在編譯時根據@GlideModule註解來生成自定義Glide的代理類GeneratedAppGlideModuleImpl
  • 通過反射獲取代理類的構造方法並實例化返回
  • 若APT方法獲取失敗則嘗試從manifest文件中獲取自定義GlideModule。manifest配置自定義module是Glide4.0以前的方式,新版本基本都採用註解APT的方式實現。
  • 若都沒有自定義的module,則通過GliderBuilder的工廠模式生成實例。
public static Glide get(@NonNull Context context) {
    if (glide == null) {
        //獲取自定義AppGlideModule的代理類GeneratedAppGlideModuleImpl   
      GeneratedAppGlideModule annotationGeneratedModule =
          getAnnotationGeneratedGlideModules(context.getApplicationContext());
      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context, annotationGeneratedModule);
        }
      }
    }
    return glide;
}

(4)Glide.getRequestManagerRetriever獲取Glide.get方法中初始化的RequestManagerRetriever對象。

public RequestManagerRetriever getRequestManagerRetriever() {
    return requestManagerRetriever;
}

(5)RequestManagerRetriever.get方法根據調用的context及是否主線程返回對應的RequestManager。context爲FragmentActivity時調用:

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)) {
    //Activity、FragmentActivity情況
      if (context instanceof FragmentActivity) {
        return get((FragmentActivity) context);
      } else if (context instanceof Activity) {
        return get((Activity) context);
      } else if (context instanceof ContextWrapper
          && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
        return get(((ContextWrapper) context).getBaseContext());
      }
    }
    //非UI線程情況
    return getApplicationManager(context);
}

如果context是Fragment時調用:

public RequestManager get(@NonNull Fragment fragment) {
    Preconditions.checkNotNull(
        fragment.getContext(),
        "You cannot start a load on a fragment before it is attached or after it is destroyed");
    //非UI線程
    if (Util.isOnBackgroundThread()) {
      return get(fragment.getContext().getApplicationContext());
    } else {
        //context爲Fragment情況
      FragmentManager fm = fragment.getChildFragmentManager();
      return supportFragmentGet(fragment.getContext(), fm, fragment, fragment.isVisible());
    }
}

總的來說分三種情況:
@1. 非UI線程
@2. UI線程Activity
@3. UI線程Fragment

@1.先來分析第一種情況,第一種情況較簡單,通過getApplicationManager返回一個全局的RequestManager單例,該RequestManager是通過ApplicationLifecycle()構造的,也就是說只能感知application的生命週期,使用時需要注意。

@NonNull
  private RequestManager getApplicationManager(@NonNull Context context) {
    if (applicationManager == null) {
      synchronized (this) {
        if (applicationManager == null) {
          Glide glide = Glide.get(context.getApplicationContext());
          applicationManager =
              factory.build(
                  glide,
                  new ApplicationLifecycle(),
                  new EmptyRequestManagerTreeNode(),
                  context.getApplicationContext());
        }
      }
    }
    return applicationManager;
}

@2.再來看UI線程Activity情況

public RequestManager get(@NonNull FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
        //斷言activity未銷燬
      assertNotDestroyed(activity);
      //獲取FragmentManager
      FragmentManager fm = activity.getSupportFragmentManager();
      //核心!!通過fm向當前activity添加一個SupportRequestManagerFragment實例
      //通過SupportRequestManagerFragment感知activity的生命週期
      return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
}

向當前activity添加一個fragment,並通過持有該fragment的lifecycle實例來感知當前activity的生命週期狀態。通過fragment感知生命週期這種套路很多地方都能看到例如LifeCycle。

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

@3. UI線程Fragment。第三種情況就不必多說了代碼和Activty大部分都是複用的,只不過區別在於獲取fm上,通過Fragment.getChildFragmentManager來獲取fm,向Fragment添加一個子Fragment。子Fragment 則會通過 ChildFragmentManager 和 Fragment 保持生命週期一致。

這樣做的好處是可以感知調用方的生命週期,避免生命週期不一致導致的內存泄漏或者空指針問題。

總結一下Glide.with()初始化過程:

  • (1)初始化Glide並獲取Glide實例
    • 先通過APT方式產生的自定義GlideModule實例化
    • 沒有則通過manifest方式自定義GlideModule實例化
    • 沒有則用GliderBuilder構造默認實例
  • (2)獲取Glide的RequestManagerRetriever
  • (3)根據是否UI線程及context類型返回對應的RequestManager

2-加載準備load()

RequestManager.load()函數並未實現圖片的加載,而是構造了一個RequestBuilder實例

protected RequestBuilder(
      @NonNull Glide glide,
      RequestManager requestManager,
      Class<TranscodeType> transcodeClass,
      Context context) {
    this.glide = glide;//glide實例
    this.requestManager = requestManager;//RequestManager實例
    this.transcodeClass = transcodeClass;//圖片加載類型,默認Drawable
    this.context = context;
    this.transitionOptions = requestManager.getDefaultTransitionOptions(transcodeClass);
    this.glideContext = glide.getGlideContext();
    //@1.初始化監聽
    initRequestListeners(requestManager.getDefaultRequestListeners());
    //@2.將設置的參數/默認參數賦值應用
    apply(requestManager.getDefaultRequestOptions());
  }

@1.初始化監聽過程則是對於設置了圖片加載監聽的情況,RequestListener主要包含了兩個回調:

  • onLoadFaild加載失敗
  • onResourceReady資源獲取成功

@2.將設置的參數/默認參數賦值應用。這裏的參數是在調用時用戶傳入或者默認參數,通過RequestOptions來保存這些配置,包括圖片的裁剪方式、兜底圖、出場動畫等配置

3-加載顯示圖片 into()

完成了with初始和load加載準備工作後,就可以開始獲取圖片並顯示到目標ImageView上了。先從RequestBuilder.into開始,這裏的RequestBuilder就是上一步load()生成的實例。

public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    //斷言是否UI線程,非UI線程拋出異常
    Util.assertMainThread();
    //判空
    Preconditions.checkNotNull(view);
    BaseRequestOptions<?> requestOptions = this;
    //獲取裁剪方式配置。注意這裏使用的是clone的方式
    //避免修改原始配置參數,導致相同RequestBuilder加載其他目標時配置被修改過。
    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.
      }
    //@1.核心!!加載圖片
    return into(
        //根據類型封裝成對應的ViewTarget
        //分爲DrawableImageViewTarget、BitmapImageViewTarget
        //分別調用ImageView的setImageDrawable、setImageBitmap來實現圖片加載顯示
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
}

@1.加載圖片into

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 = buildRequest(target, targetListener, options, callbackExecutor);

    Request previous = target.getRequest();
    // 這裏做了請求優化處理,避免重複資源請求。同時滿足如下兩個條件時,直接複用前一個request。
    //1.當前request和前一個request相同
    //2.支持內存緩存cacheable=true或者前一個請求未成功完成isComplete=false
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      //如果前一個request處於非running狀態,重新啓動該請求
      if (!Preconditions.checkNotNull(previous).isRunning()) {
        previous.begin();
      }
      return target;
    }
    //如果是新請求,更新RequestTracker及TargetTracker
    requestManager.clear(target);
    target.setRequest(request);
    //@2.發送加載請求
    requestManager.track(target, request);

    return target;
}

@2.發送加載請求。RequestManager.track方法

synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
    //存儲ViewTarget
    targetTracker.track(target);
    //@3.將request加入請求集合並執行
    requestTracker.runRequest(request);
}

這裏涉及到的兩個比較重要的類RequestTracker及TargetTracker。分別維護請求集合和加載目標集合

  • TargetTracker比較好理解,主要維護了同一個context下所有ViewTarget的集合,並將context的lifecycle的生命週期變化同步給所有ViewTarget,持有的是ViewTarget的弱引用
    • Set<Target<?>> targets =
      Collections.newSetFromMap(new WeakHashMap<Target<?>, Boolean>());
  • RequestTracker是管理當前context下所有的圖片加載請求任務Request。包含兩個集合,requests保存待發起請求,pendingRequest存放中斷狀態下的請求。pendingRequest是用強引用List存儲,便於恢復執行未完成的請求。
    • Set requests =
      Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());
    • List pendingRequests = new ArrayList<>();

@3.將request加入請求集合並執行。RequestTracker.runRequest

public void runRequest(@NonNull Request request) {
    //加入待執行請求集合requests
    requests.add(request);
    if (!isPaused) {
        //@4.非中斷狀態,請求執行
      request.begin();
    } else {
        //中斷狀態,請求狀態清空並存入pendingRequests
      request.clear();
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
      pendingRequests.add(request);
    }
}

isPaused狀態依賴於context的lifecycle狀態。若是ApplicationLifeCyle則app正常運行狀態下isPaused=false;若是Fragment/Activity則在其LifeCycle對應的活躍狀態下isPaused=false,非活躍狀態isPaused=true。

@4.request.begin請求任務的執行,Request只是一個接口,這裏具體實現類是SingleRequest。加載任務主要分兩步,第一步測量加載尺寸,第二步進行加載

public void begin() {
    synchronized (requestLock) {
      assertNotCallingCallbacks();
      stateVerifier.throwIfRecycled();
      startTime = LogTime.getLogTime();
      。。。//省略

      status = Status.WAITING_FOR_SIZE;
      //@2.若指定了寬高,且尺寸有效測量完畢
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
      } else {
        //未指定寬高,則測量綁定ImageView的寬高,然後還是走到onSizeReady方法
        //同時這裏還會通過ViewTreeObserver監聽ImageView的尺寸變化,
        //尺寸變化時通知調整圖片尺寸
        target.getSize(this);
      }
      //@1.回調ViewTarget的onLoadStarted方法,並返回佔位圖
      if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
          && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
      }
      。。。
    }
}

@1.ImageViewTarget.onLoadStarted方法中加載佔位圖,方法比較簡單

@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
    super.onLoadStarted(placeholder);
    setResourceInternal(null);
    setDrawable(placeholder);
}

@2.圖片尺寸測量完畢後就開始加載圖片了,下SingleRequest.onSizeReady方法中主要是通過調用Engine.load方法來加載圖片。Engine即圖片加載引擎。調用鏈SingleRequest.onSizeReady–>Engine.load

public <R> LoadStatus load(。。。//入參省略) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    。。。//省略

    EngineResource<?> memoryResource;
    synchronized (this) {
        //@3.從內存/硬盤緩存加載
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

      if (memoryResource == null) {
        //@4.緩存中沒有,則通過引擎加載
        return waitForExistingOrStartNewJob(。。。//入參省略);
      }
    }

    //@5.回調ResourceCallback.onResourceReady方法,獲取圖片資源完成
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
    return null;
}

無論是通過緩存@3還是通過網絡下載@4,最終都會通過ResourceCallback.onResourceReady來完成圖片獲取回調,所以先從@5接着分析完整個流程鏈路。@3和@4涉及到Glide的核心-三級緩存機制,下節詳細講述。

@5.(1)回調ResourceCallback.onResourceReady

–>(2)SingleRequest.onResourceReady

  • 回調對SingleRequest設置的RequestListener.onResourceReady
  • 構建轉場動畫並回調taget.onResourceReady,這裏的target即之前的DrawableImageViewTarget,回調方法在其父類.onResourceReady


–>(3)ImageViewTarget.onResourceReady

public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
        //調用DrawableImageViewTarget.setResource及maybeUpdateAnimatable
      setResourceInternal(resource);
    } else {
        //若爲AnimationDrawable等繼承了Animatable接口的Drawable,執行其動畫
      maybeUpdateAnimatable(resource);
    }
}


–>(4)DrawableImageViewTarget.setResource

–>(5)ImageView.setImageDrawable

至此整個Glide.with(this).load(url).into(mainPic);流程就結束了,由於是限制在UI線程,也沒有線程切換的操作,通過熟悉的ImageView.setImageDrawable將圖片展示到界面。

RequestBuilder.inot加載圖片過程總結:

  • (1)RequestBuilder.into構建新的請求Request或啓動上次相同Request,克隆圖片裁剪方式配置到Request
  • (2)RequestManager.track添加ViewTarget到TargetTracker,添加Request到RequestTracker並啓動request
  • (3)SingleRequest.begin開啓請求
    • 測量階段,通過用戶設置的寬高或獲取目標ImageView寬高
    • 測量完畢調用onResourceReady–>Engin.load加載圖片
  • (4)Engin.load通過Glide的三級緩存機制先從緩存中獲取目標資源,否則通過網絡請求資源
  • (5)資源獲取完畢調用ImageViewTarget.onResourceReady,在回調中設置Drawable到目標ImageView完成加載顯示

4-Glide三級緩存機制

再回到第3節中的Engin.load方法中,@3.從內存/硬盤緩存加載。內存緩存也分了兩級:

  • ActiveResources。活躍緩存池,正在使用的EngineResource對象,弱引用緩存。資源釋放時緩存進入LruResourceCache
  • LruResourceCache。內存緩存池,LruCache管理的EngineResource緩存。獲取緩存成功時,從LruResourceCache移除該緩存,並添加到ActiveResources

key-EngineKey:

EngineKey封裝了加載圖片的uri、寬高等信息。

value-EngineResource:

EngineResource是個泛型類封裝了圖片資源(根據ViewTarget的類型可以是Drawable也可以是Bitmap),及引用計數acquired,這個引用計數很關鍵,緩存被引用時計數+1。當某個圖片加載完畢時嘗試釋放資源時要根據該引用來決定是否可以釋放。

private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }
    //@6.活躍緩存-ActiveResources
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }
    //@7.若從活躍緩存池activeResources中獲取資源失敗
    //則從內存緩存-LruResourceCache中獲取
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }

    return null;
}

@6.活躍緩存-ActiveResources。通過持有當前使用的EngineResource對象的弱引用來實現緩存。若命中則active.acquire()引用計數+1。

private EngineResource<?> loadFromActiveResources(Key key) {
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }
    return active;
}

@7.內存緩存-LruResourceCache。通過LruCache實現的圖片資源緩存。這個LruResourceCache是在初始化時GlideBuilder默認生成的,也可以設置自定義內存緩存

Glide build(@NonNull Context context) {
。。。//代碼省略
    //內存緩存
    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
    //硬盤緩存,具體實現是其DiskLruCacheWrapper,後面會講到
    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }
。。。//代碼省略
}
private EngineResource<?> loadFromCache(Key key) {
    //從LruResourceCache中獲取resource,並從該緩存中移除
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();//引用計數+1
      activeResources.activate(key, cached);//存入活躍緩存池
    }
    return cached;
}

若兩級內存緩存獲取資源失敗,再回到第3節中@4處發起獲取資源任務waitForExistingOrStartNewJob

private <R> LoadStatus waitForExistingOrStartNewJob(。。。//形參省略) {

    。。。//代碼省略
    //構建加載任務
    EngineJob<R> engineJob =
        engineJobFactory.build(。。。//實參省略);
    //構建任務的子任務解析圖片任務
    DecodeJob<R> decodeJob =
        decodeJobFactory.build(。。。//實參省略);

    jobs.put(key, engineJob);
    //添加任務回調
    engineJob.addCallback(cb, callbackExecutor);
    //@8.通過線程池開啓任務
    engineJob.start(decodeJob);

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

@8.通過線程池開啓任務。這裏開始調用鏈路太長了,這裏按下⏩挑重點來講吧,避免鑽入源碼黑洞。。。:

  • –>Engine.waitForExistingOrStartNewJob
  • –>EngineJob.start
  • –>DecodeJob.run–>runWrapped–>runGenerators
  • –>DataFetcherGenerator.start
    • (a)ResourceCacheGenerator.startNext
    • (b)SourceGenerator.startNext

在這裏打斷一下,開始出現了分支。

(a)硬盤緩存:ResourceCacheGenerator.startNext從硬盤緩存中
。在runGenerators這個函數中出現了硬盤緩存邏輯。Glide默認DiskLruCacheWrapper實現,原理類似DiscLruCache同樣使用LRU策略管理緩存文件,且可以通過設置DiskCacheStrategy來決定緩存原圖文件還是解碼後的圖片。

(b)網絡加載:若硬盤緩存獲取失敗,SourceGenerator.startNext通過網絡請求下載圖片,具體實現是HttpUrlFetcher。HttpURLConnection建立網絡連接,下載圖片、解碼、壓縮、並根據緩存策略存儲到硬盤緩存中、更新到內存緩存。這部分涉及代碼較多就不展開講了。。。

  • –>DataFetcherGenerator.onDataReady
  • –>DecodeJob.onDataFetcherReady
  • –>EncodeJob.onResourceReady。在這裏會更新圖片資源到活躍內存緩存activeResources

後面的流程就回到了第3節@5中了,不再贅述

  • –>ResourceCallback.onResourceReady
  • –>SingleRequest.onResourceReady
  • –>ImageViewTarget.onResourceReady
  • –>DrawableImageViewTarget.setResource
  • –>ImageView.setImageDrawable

總結下Glide的三級緩存策略:

  • (1)Engine.load中先從內存緩存中加載圖片loadFromMemory
    • 先從actives活躍緩存中獲取
    • 獲取失敗從內存緩存cache中獲取,同時將緩存從cache中移到actives中
  • (2)內存緩存獲取失敗,則構建EngineJob並執行start
  • (3)執行DecodeEngine.run通過ResourceCacheGenerator從硬盤緩存中讀取
  • (4)讀取失敗則通過SourceGenerator執行獲取任務,下載網絡圖片
  • (5)下載成功後經過解碼、壓縮後,更新硬盤緩存及活躍緩存actives
  • (6)回調ImageViewTarget最終設置並顯示圖片到目標ImageView

5-總結

最後總結下Glide.with(this).load(url).into(mainPic)這行代碼中Glide從初始化到加載顯示圖片的整個流程:
流程圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章