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
可以把它理解為 DraweeView
、DraweeHierachy
和 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
我們經過追蹤,它的實例實際上是CloseableProducerToDataSourceAdapter
。CloseableProducerToDataSourceAdapter
在構造時就會調用 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
為 ProducerA
,NetworkFetchProducer
為 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 和圖片加載過程,希望通過閱讀其源碼來詳細了解內部的代碼邏輯以及設計思路。
其實我們在閱讀別人源碼的時候,除了要知道具體的細節之外,也要注意別人的模塊設計,借鑒其設計思想。然后想想如果是你在設計的時候,你會怎么划分模塊,如何將不同的模塊聯系起來。
當模塊划分后,里面的子模塊又是如何划分的,它們之間協作關系如何保持。