Fresco 源碼分析 —— 整體架構

Fresco 是我們項目中圖片載入專用框架。雖然我不是負責 Fresco 框架,但是由本人負責組裡的圖片載入瀏覽等工作,因此了解 Fresco 的源碼有助於我今後的工作,也可以學習 Fresco 的源碼設計精髓。

由於 Fresco 源碼比較多,僅憑一篇文章是無法將其說清楚的,因此會當做一個系列,詳細介紹 Fresco 源碼。本系列文章也會參考網上關於 Fresco 源碼解析的文章,儘可能準確的去描述 Fresco 的實現原理,如有錯誤之處歡迎指出,歡迎交流學習。

Fresco 是一個強大的圖片載入組件。使用它之後,你不需要再去關心圖片的載入和顯示這些繁瑣的事情! 支援 Android 2.3 及以後的版本。如果需要了解 Fresco 的使用可以訪問 Fresco 使用文檔 。

Fresco是一個功能完善的圖片載入框架,在Android開發中有著廣泛的應用,那麼它作為一個圖片載入框架,有哪些特色讓它備受推崇呢?

  • 完善的記憶體管理功能,減少圖片對記憶體的佔用,即便在低端機器上也有著不錯的表現。

  • 自定義圖片載入的過程,可以先顯示低清晰度圖片或者縮略圖,載入完成後再顯示高清圖,可以在載入的時候縮放和旋轉圖片。

  • 自定義圖片繪製的過程,可以自定義谷中焦點、圓角圖、占點陣圖、overlay、進圖條。

  • 漸進式顯示圖片。

  • 支援Gif。

  • 支援Webp。

  • ……

Fresco 的組成結構還是比較清晰的,大致如下圖所示:

      

其實這兩張圖來自不同的文章,但是我覺得兩者的分層實際上基本是一樣的。只是一個比較概括,一個比價具體,將兩者擺在一起,更有助於大家去理解其實現細節。當然除了 UI 和載入顯示部分外,還有 Gif,動態圖片等內容,以及對應圖片解碼編碼邏輯等。這部分不打算去講解,因為這部分雖然也是源碼很重要的一部分,但是這部分需要相關專業知識才好說明白,此外且涉及到 C++ 程式碼。

下面結合程式碼分別解釋一下上面各模組的作用以及大概的工作原理。

DraweeView

它繼承自 ImageView,是 Fresco 載入圖片各個階段過程中圖片顯示的載體,比如在載入圖片過程中它顯示的是占點陣圖、在載入成功時切換為目標圖片。不過後續官方可能不再讓這個類繼承 ImageView,所以該類並不支援 ImageView 的 setImageXxx, setScaleType 以及其他類似的方法。目前 DraweeView 與 ImageView 唯一的交集是:它利用 ImageView 來顯示 Drawable :

//DraweeView.setController()
public void setController(@Nullable DraweeController draweeController) {
    mDraweeHolder.setController(draweeController);
    super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());  //super 就是 ImageView
}

//DraweeHolder.getTopLevelDrawable()
public @Nullable Drawable getTopLevelDrawable() {
    return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable(); // mHierarchy 是 DraweeHierachy,
}

DraweeView.setController() 會在 Fresco 載入圖片時會調用。其實在這裡可以看出 Fresco 的圖片顯示原理是 : 利用 ImageView 顯示DraweeHierachy 的 TopLevelDrawable。上面這段程式碼引出了 UI 層中另外兩個關鍵類: DraweeHolder 和 DraweeHierachy

DraweeHierachy

可以說它是 Fresco 圖片顯示的實現者。它的輸出是 Drawable,這個 Drawable 會被 DraweeView 拿來顯示(上面已經說了)。它內部有多個 Drawable,當前顯示在 DraweeView 的 Drawable 叫做 TopLevelDrawable。在不同的圖片載入階段,TopLevelDrawable 是不同的(比如載入過程中是 placeholder,載入完成是目標圖片)。具體的 Drawable 切換邏輯是由它來具體實現的。

它是由 DraweeController 直接持有的,因此對於不同圖片顯示的切換操作具體是由 DraweeController 來直接操作的。

DraweeHolder

可以把它理解為 DraweeViewDraweeHierachy 和 DraweeController 這 3 個類之間的粘合劑, DraweeView 並不直接和 DraweeController 和 DraweeHierachy 直接接觸,所有的操作都是通過它傳過去。這樣,後續將 DraweeView 的父類改為 View,也不會影響到其他類。DraweeView 作為 View 可以感知點擊和生命周期,通過 DraweeHolder 來控制其他兩個類的操作。

想想如果是你,你會抽出 DraweeHolder 這樣一個類嗎?實際上,這裡對我們平時開發也是有所借鑒,嚴格控制每一個類之間的關係,可以引入一些一些中間類,讓類與類之間的關係耦合度降低,方便日後迭代。

具體引用關係如下圖: 

它的主要功能是: 接收 DraweeView 的圖片載入請求,控制 ProducerSequence 發起圖片載入和處理流程,監聽 ProducerSequence 載入過程中的事件(失敗、完成等),並更新最新的 Drawable 到 DraweeHierachy

DraweeController 的構造邏輯

在 Fresco 中 DraweeController 是通過 PipelineDraweeControllerBuilderSupplier 獲取的。Fresco在初始化時會調用下面的程式碼:

// Fresco.java
private static void initializeDrawee(Context context, @Nullable DraweeConfig draweeConfig) {
    sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
    SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}

sDraweeControllerBuilderSupplier 是靜態變數,也就是說其在只會初始一次。所有的 DraweeController 都是通過調用 sDraweecontrollerbuildersupplier.get() 得到的。

  private void init(Context context, @Nullable AttributeSet attrs) {
    try {
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.beginSection("SimpleDraweeView#init");
      }
      if (isInEditMode()) {
        getTopLevelDrawable().setVisible(true, false);
        getTopLevelDrawable().invalidateSelf();
      } else {
        Preconditions.checkNotNull(
            sDraweecontrollerbuildersupplier, "SimpleDraweeView was not initialized!");
        mControllerBuilder = sDraweecontrollerbuildersupplier.get(); // 調用一次就會創建一個新的實例
    }
    // ...... 省略其他程式碼  
}

Fresco 每次圖片載入都會對應到一個 DraweeController,一個DraweeView的多次圖片載入可以復用同一個DraweeController:

SimpleDraweeView.java

public void setImageURI(Uri uri, @Nullable Object callerContext) {
    DraweeController controller =
        mControllerBuilder
            .setCallerContext(callerContext)
            .setUri(uri) //設置新的圖片載入路徑
            .setOldController(getController())  //復用 controller
            .build(); 
    setController(controller);
}

所以一般情況下 : 一個 DraweeView 對應一個 DraweeController

通過 DataSource 發起圖片載入

在前面已經說了 DraweeController 是直接持有 DraweeHierachy,所以它觀察到 ProducerSequence 的數據變化是可以很容易更新到 DraweeHierachy(具體程式碼先不展示了)。那它是如何控制 ProducerSequence 來載入圖片的呢?其實 DraweeController 並不會直接和 ProducerSequence 發生關聯。對於圖片的載入,它直接接觸的是 DataSource,由 DataSource 進而來控制 ProducerSequence 發起圖片載入和處理流程。下面就跟隨源碼來看一下 DraweeController 是如果通過 DataSource 來控制 ProducerSequence 發起圖片載入和處理流程的。

// AbstractDraweeController.java
protected void submitRequest() {
    mDataSource = getDataSource(); 
    final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { //可以簡單的把它理解為一個監聽者
        @Override
        public void onNewResultImpl(DataSource<T> dataSource) { //圖片載入成功
            ...
        }
        ...
    };
    ...
    mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回調方法運行的執行緒,這裡是主執行緒
}

那 DataSource 是什麼呢? getDataSource()最終會調用到:

// PipelineDraweeControllerBuilder 
 protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(
      DraweeController controller,
      String controllerId,
      ImageRequest imageRequest,
      Object callerContext,
      AbstractDraweeControllerBuilder.CacheLevel cacheLevel) {
    return mImagePipeline.fetchDecodedImage(
        imageRequest,
        callerContext,
        convertCacheLevelToRequestLevel(cacheLevel),
        getRequestListener(controller),
        controllerId);
  }

 

// CloseableProducerToDataSourceAdapter<T>
public static <T> DataSource<CloseableReference<T>> create(
      Producer<CloseableReference<T>> producer,
      SettableProducerContext settableProducerContext,
      RequestListener2 listener) {
   
    CloseableProducerToDataSourceAdapter<T> result =
        new CloseableProducerToDataSourceAdapter<T>(producer, settableProducerContext, listener);return result;
  }

所以 DraweeController 最終拿到的 DataSource 是 CloseableProducerToDataSourceAdapter。這個類在構造的時候就會啟動圖片載入流程(它的構造方法會調用producer.produceResults(...),這個方法就是圖片載入的起點,我們後面再看)。

這裡我們總結一下 Fresco 中 DataSource 的概念以及作用: 在 Fresco 中 DraweeController 每發起一次圖片載入就會創建一個 DataSource,這個 DataSource 用來提供這次請求的數據(圖片)。DataSource 只是一個介面,至於具體的載入流程 Fresco 是通過 ProducerSequence 來實現的。

Fresco圖片載入前的邏輯

了解了上面的知識後,我們過一遍圖片載入的源碼(從 UI 到 DraweeController),來理一下目前所了解的各個模組之間的聯繫。我們在使用 Fresco 載入圖片時一般是使用這個API: SimpleDraweeView.setImageURI(imageLink),這個方法最終會調用到:

// SimpleDraweeView.java
public void setImageURI(Uri uri, @Nullable Object callerContext) {
    DraweeController controller = mControllerBuilder
            .setCallerContext(callerContext)
            .setUri(uri)
            .setOldController(getController())
            .build();    //這裡會復用 controller
    setController(controller);
}

public void setController(@Nullable DraweeController draweeController) {
    mDraweeHolder.setController(draweeController);
    super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());  
}

即每次載入都會使用 DraweeControllerBuilder 來 build 一個 DraweeController。其實這個 DraweeController 默認是復用的,這裡的復用針對的是同一個 SimpleDraweeView

 。然後會把 DraweeController 設置給 DraweeHolder,並在載入開始默認是從 DraweeHolder 獲取 TopLevelDrawable 並展示到 DraweeView。繼續看一下 DraweeHolder 的邏輯:

// DraweeHolder.java
public @Nullable Drawable getTopLevelDrawable() {
    return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable();
}

/** Sets a new controller. */
public void setController(@Nullable DraweeController draweeController) {
boolean wasAttached = mIsControllerAttached;
if (wasAttached) {
detachController();
}

// Clear the old controller
if (isControllerValid()) {
mEventTracker.recordEvent(Event.ON_CLEAR_OLD_CONTROLLER);
mController.setHierarchy(null);
}
mController = draweeController;
 // 注意這裡是只有確定已經 attached 才會調用,也就是才回去載入圖片
if (wasAttached) {
attachController();
}
}

DraweeHolder.setController() 中把 DraweeHierachy 設置給 DraweeController,並重新 attachController(),attachController()主要調用了DraweeController.onAttach():

// AbstractDraweeController.java
public void onAttach() {
    ...
    mIsAttached = true;
    if (!mIsRequestSubmitted) {
      submitRequest();
    }
}

protected void submitRequest() {
    mDataSource = getDataSource(); 
    final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { //可以簡單的把它理解為一個監聽者
        @Override
        public void onNewResultImpl(DataSource<T> dataSource) { //圖片載入成功
            ...
        }
        ...
    };
    ...
    mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回調方法運行的執行緒,這裡是主執行緒
}

即通過submitRequest()提交了一個請求,這個方法我們前面已經看過了,它所做的主要事情就是,構造了一個DataSource。這個 DataSource 我們經過追蹤,它的實例實際上是CloseableProducerToDataSourceAdapterCloseableProducerToDataSourceAdapter 在構造時就會調用 producer.produceResults(...),進而發起整個圖片載入流程。

用下面這張圖總結從SimpleDraweeView->DraweeController的圖片載入邏輯:

到這裡我們梳理完了 Fresco 在真正發起圖片載入前所走的邏輯,那麼 Fresco 的圖片載入流程是如何控制的呢?到底經歷了哪些步驟呢?

Producer

Fresco中有關圖片的記憶體快取、解碼、編碼、磁碟快取、網路請求都是在這一層實現的,而所有的實現的基本單元是 Producer,所以我們先來理解一下 Producer:

看一下它的定義:

/**
 * <p> Execution of image request consists of multiple different tasks such as network fetch,
 * disk caching, memory caching, decoding, applying transformations etc. Producer<T> represents
 * single task whose result is an instance of T. Breaking entire request into sequence of
 * Producers allows us to construct different requests while reusing the same blocks.
 */
public interface Producer<T> {

  /**
   * Start producing results for given context. Provided consumer is notified whenever progress is made (new value is ready or error occurs).
   */
  void produceResults(Consumer<T> consumer, ProducerContext context);
}

結合注釋我們可以這樣定義 Producer 的作用:一個 Producer 用來處理整個 Fresco 圖片處理流程中的一步,比如從網路獲取圖片、記憶體獲取圖片、解碼圖片等等。而對於 Consumer 可以把它理解為監聽者,看一下它的定義:

public interface Consumer<T> {
/**
   * Called by a producer whenever new data is produced. This method should not throw an exception.
   *
   * <p>In case when result is closeable resource producer will close it after onNewResult returns.
   * Consumer needs to make copy of it if the resource must be accessed after that. Fortunately,
   * with CloseableReferences, that should not impose too much overhead.
   *
   * @param newResult
   * @param status bitwise values describing the returned result
   * @see Status for status flags
   */
  void onNewResult(T newResult, @Status int status);

  /**
   * Called by a producer whenever it terminates further work due to Throwable being thrown. This
   * method should not throw an exception.
   *
   * @param t
   */
  void onFailure(Throwable t);

  /** Called by a producer whenever it is cancelled and won't produce any more results */
  void onCancellation();

  /**
   * Called when the progress updates.
   *
   * @param progress in range [0, 1]
   */
  void onProgressUpdate(float progress);
}

Producer 的處理結果可以通過 Consumer 來告訴外界,比如是失敗還是成功。

Producer 的組合

一個 ProducerA 可以接收另一個 ProducerB 作為參數,如果 ProducerA 處理完畢後可以調用 ProducerB 來繼續處理。並傳入 Consumer 來觀察 ProducerB 的處理結果。比如Fresco 在載入圖片時會先去記憶體快取獲取,如果記憶體快取中沒有那麼就網路載入。這裡涉及到兩個 Producer 分別是 BitmapMemoryCacheProducer 和 NetworkFetchProducer,假設BitmapMemoryCacheProducer 為 ProducerANetworkFetchProducer 為 ProducerB。我們用偽程式碼看一下他們的邏輯:

// BitmapMemoryCacheProducer.java

public class BitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> {

    private final Producer<CloseableReference<CloseableImage>> mInputProducer;

    // 我們假設 inputProducer 在這裡為NetworkFetchProducer
    public BitmapMemoryCacheProducer(...,Producer<CloseableReference<CloseableImage>> inputProducer) { 
        ...
        mInputProducer = inputProducer;
    }

    @Override
    public void produceResults(Consumer<CloseableReference<CloseableImage>> consumer,...) {
        CloseableReference<CloseableImage> cachedReference = mMemoryCache.get(cacheKey);

        if (cachedReference != null) { //從快取中獲取成功,直接通知外界
            consumer.onNewResult(cachedReference, BaseConsumer.simpleStatusForIsLast(isFinal));
            return; //結束處理流程
        }

        Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(consumer..); //包了一層Consumer,即mInputProducer產生結果時,它自己可以觀察到
        mInputProducer.produceResults(wrappedConsumer, producerContext); //網路載入
    }
}

 

// NetworkFetchProducer.java

public class NetworkFetchProducer implements Producer<EncodedImage> {

   // 它並沒有 inputProducer, 對於 Fresco 的圖片載入來說如果網路都獲取失敗,那麼就是圖片載入失敗了

    @Override
    public void produceResults(final Consumer<CloseableReference<CloseableImage>> consumer,..) {

       // 網路獲取
       //   ...
        if(獲取到網路圖片){
            notifyConsumer(...); //把結果通知給consumer,即觀察者
        }
        ...
    }
}

程式碼可能不是很好理解,可以結合下面這張圖來理解這個關係:

Fresco 可以通過組裝多個不同的 Producer 來靈活的定義不同的圖片處理流程的,多個 Producer 組裝在一塊稱為 ProducerSequence (Fresco 中並沒有這個類哦)。一個ProducerSequence 一般定義一種圖片處理流程,比如網路載入圖片的 ProducerSequence 叫做 NetworkFetchSequence,它包含多個不同類型的 Producer

網路圖片載入的處理流程

在 Fresco 中不同的圖片請求會有不同的 ProducerSequence 來處理,比如網路圖片請求:

// ProducerSequenceFactory.java
private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(ImageRequest imageRequest) {
    switch (imageRequest.getSourceUriType()) {
        case SOURCE_TYPE_NETWORK: return getNetworkFetchSequence();
        ...
} 

所以對於網路圖片請求會調用 getNetworkFetchSequence:

/**
* swallow result if prefetch -> bitmap cache get -> background thread hand-off -> multiplex ->
* bitmap cache -> decode -> multiplex -> encoded cache -> disk cache -> (webp transcode) ->
* network fetch.
*/
private synchronized Producer<CloseableReference<CloseableImage>> getNetworkFetchSequence() {
    ...
    mNetworkFetchSequence = new BitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence());
    ...
    return mNetworkFetchSequence;
}

getNetworkFetchSequence 會經過重重調用來組合多個 Producer。這裡我就不追程式碼邏輯了,直接用下面這張圖來描述 Fresco 網路載入圖片的處理流程:

 

可以看到 Fresco 的整個圖片載入過程還是十分複雜的。並且上圖我只是羅列一些關鍵的 Producer,其實還有一些我沒有畫出來。 

總結

為了輔助理解,再提供一張總結的流程圖,將上面整個過程都放在裡面了。後續的系列文章會詳細介紹 UI 和圖片載入過程,希望通過閱讀其源碼來詳細了解內部的程式碼邏輯以及設計思路。

其實我們在閱讀別人源碼的時候,除了要知道具體的細節之外,也要注意別人的模組設計,借鑒其設計思想。然後想想如果是你在設計的時候,你會怎麼劃分模組,如何將不同的模組聯繫起來。

當模組劃分後,裡面的子模組又是如何劃分的,它們之間協作關係如何保持。

 

參考文章

Android開源框架源碼鑒賞:Fresco

Fresco架構設計賞析 

Tags: