Fresco架構設計賞析
本文是 Fresco
原始碼分析系列的開篇,主要分析 Fresco
的整體架構、各個組成模組的功能以及圖片載入流程,希望通過本文可以對 Fresco
的整體框架設計有一個大概的瞭解,也為後續更為深入的分析打下基礎。
Fresco
原始碼龐大,涉及的圖片載入情況眾多。本系列 Fresco
原始碼分析是沿著 Fresco網路載入圖片 這個點展開的。
Fresco的整體架構
Fresco
的組成結構還是比較清晰的,大致如下圖所示:

Fresco組成結構.png
下面結合程式碼分別解釋一下上面各模組的作用以及大概的工作原理。
UI層
DraweeView
它繼承自 ImageView
,是 Fresco
載入圖片各個階段過程中圖片顯示的載體,比如在載入圖片過程中它顯示的是佔位圖、在載入成功時切換為目標圖片。不過後續官方可能不再讓這個類繼承 ImageView
。目前 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
和 DraweeController
的 attach
關係(DraweeView只有attch了DraweeController才會具體載入網路圖片的能力)。可以把它理解為 DraweeView
、 DraweeHierachy
和 DraweeController
這3個類之間的粘合劑,具體引用關係如下圖:

DraweeHolder對於UI層的粘合.png
DraweeController : 載入邏輯控制層
它的主要功能是: 接收 DraweeView
的圖片載入請求,控制 ProducerSequence
發起圖片載入和處理流程,監聽 ProducerSequence
載入過程中的事件(失敗、完成等),並更新最新的 Drawable
到 DraweeHierachy
。
DraweeController的構造邏輯
在 Fresco
中 DraweeController
是通過 DraweeControllerBuilder
來構造的。而 DraweeControllerBuilder
在 Fresco
中是以單例的形式存在的。 Fresco
在初始化時會呼叫下面的程式碼:
Fresco.java
private static void initializeDrawee(Context context, @Nullable DraweeConfig draweeConfig) { sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context, draweeConfig); SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier); }
所以所有的 DraweeController
都是通過同一個 DraweeControllerBuilder
來構造的。 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
發起圖片載入和處理流程的。
DraweeController發起圖片載入請求的方法是(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()
最終會呼叫到:
ImagePipeline.java
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(ImageRequest imageRequest,...) { //獲取載入圖片的ProducerSequence Producer<CloseableReference<CloseableImage>> producerSequence = mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest); return submitFetchRequest( producerSequence, imageRequest, lowestPermittedRequestLevelOnSubmit, callerContext, requestListener); } private <T> DataSource<CloseableReference<T>> submitFetchRequest(...) { ... return CloseableProducerToDataSourceAdapter.create(roducerSequence, settableProducerContext, finalRequestListener); }
所以 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
預設是複用的。然後會把 DraweeController
設定給 DraweeHolder
, 並在載入開始預設是從 DraweeHolder
獲取 TopLevelDrawable
並展示到 DraweeView
。繼續看一下 DraweeHolder
的邏輯:
DraweeHolder.java
public @Nullable Drawable getTopLevelDrawable() { return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable(); } public void setController(@Nullable DraweeController draweeController) { detachController(); mController = draweeController; ... mController.setHierarchy(mHierarchy); 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
的圖片載入邏輯:

圖片載入之前的邏輯.png
到這裡我們梳理完了 Fresco
在真正發起圖片載入前所走的邏輯,那麼 Fresco
的圖片載入流程是如何控制的呢?到底經歷了哪些步驟呢?
圖片載入實現層
Fresco
中有關圖片的記憶體快取、解碼、編碼、磁碟快取、網路請求都是在這一層實現的,而所有的實現的基本單元是 Producer
,所以我們先來理解一下 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> { ... void onNewResult(T newResult, @Status int status); //Producer處理成功 void onFailure(Throwable t); //Producer處理失敗 ... }
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,即觀察者 } ... } }
程式碼可能不是很好理解,可以結合下面這張圖來理解這個關係:

Producer的工作邏輯.png
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
網路載入圖片的處理流程:

NetworkFetchSequence.png
可以看到 Fresco
的整個圖片載入過程還是十分複雜的。並且上圖我只是羅列一些關鍵的 Producer
,其實還有一些我沒有畫出來,有興趣可以去原始碼細細探討一下。
OK,到這裡本文算是結束了,希望你可以通過本文對 Fresco
的設計在整體上有一定的瞭解。後續文章會繼續討論 Fresco
的快取邏輯、圖片壓縮、 DraweeHierachy
的 Drawable
切換邏輯等。歡迎繼續關注。
歡迎關注我的 Android進階計劃 看更多幹貨
歡迎關注我的微信公眾號:susion隨心

微信公眾號.jpeg