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
rawable
:
//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 和圖片載入過程,希望通過閱讀其原始碼來詳細瞭解內部的程式碼邏輯以及設計思路。
其實我們在閱讀別人原始碼的時候,除了要知道具體的細節之外,也要注意別人的模組設計,借鑑其設計思想。然後想想如果是你在設計的時候,你會怎麼劃分模組,如何將不同的模組聯絡起來。
當模組劃分後,裡面的子模組又是如何劃分的,它們之間協作關係如何保持。