詳細(xì)分析Fresco源碼之圖片加載流程
一、概述
Fresco 是一個強大的圖片加載組件。使用它之后,你不需要再去關(guān)心圖片的加載和顯示這些繁瑣的事情! 支持 Android 2.3 及以后的版本。如果需要了解 Fresco 的使用可以訪問 Fresco 使用文檔。
Fresco是一個功能完善的圖片加載框架,在Android開發(fā)中有著廣泛的應(yīng)用,那么它作為一個圖片加載框架,有哪些特色讓它備受推崇呢?
- 完善的內(nèi)存管理功能,減少圖片對內(nèi)存的占用,即便在低端機器上也有著不錯的表現(xiàn)。
- 自定義圖片加載的過程,可以先顯示低清晰度圖片或者縮略圖,加載完成后再顯示高清圖,可以在加載的時候縮放和旋轉(zhuǎn)圖片。
- 自定義圖片繪制的過程,可以自定義谷中焦點、圓角圖、占位圖、overlay、進(jìn)圖條。
- 漸進(jìn)式顯示圖片。
- 支持Gif。
- 支持Webp。
Fresco的組成結(jié)構(gòu)還是比較清晰的,大致如下圖所示:


其實這兩張圖來自不同的文章,但是我覺得兩者的分層實際上基本是一樣的。只是一個比較概括,一個比價具體,將兩者擺在一起,更有助于大家去理解其實現(xiàn)細(xì)節(jié)。當(dāng)然除了 UI 和加載顯示部分外,還有 Gif,動態(tài)圖片等內(nèi)容,以及對應(yīng)圖片解碼編碼邏輯等。這部分不打算去講解,因為這部分雖然也是源碼很重要的一部分,但是這部分需要相關(guān)專業(yè)知識才好說明白,此外且涉及到 C++ 代碼。
下面結(jié)合代碼分別解釋一下上面各模塊的作用以及大概的工作原理。
二、DraweeView
它繼承自ImageView,是Fresco加載圖片各個階段過程中圖片顯示的載體,比如在加載圖片過程中它顯示的是占位圖、在加載成功時切換為目標(biāo)圖片。不過后續(xù)官方可能不再讓這個類繼承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加載圖片時會調(diào)用。其實在這里可以看出Fresco的圖片顯示原理是 :利用ImageView顯示DraweeHierachy的TopLevelDrawable。上面這段代碼引出了UI 層中另外兩個關(guān)鍵類:DraweeHolder和DraweeHierachy。
三、DraweeHierachy
可以說它是Fresco圖片顯示的實現(xiàn)者。它的輸出是Drawable,這個Drawable會被DraweeView拿來顯示(上面已經(jīng)說了)。它內(nèi)部有多個Drawable,當(dāng)前顯示在DraweeView的Drawable叫做TopLevelDrawable。在不同的圖片加載階段,TopLevelDrawable是不同的(比如加載過程中是 placeholder,加載完成是目標(biāo)圖片)。具體的Drawable切換邏輯是由它來具體實現(xiàn)的。
它是由DraweeController直接持有的,因此對于不同圖片顯示的切換操作具體是由DraweeController來直接操作的。
四、DraweeHolder
可以把它理解為DraweeView、DraweeHierachy和DraweeController這 3 個類之間的粘合劑, DraweeView 并不直接和DraweeController 和DraweeHierachy 直接接觸,所有的操作都是通過它傳過去。這樣,后續(xù)將 DraweeView 的父類改為 View,也不會影響到其他類。DraweeView 作為 View 可以感知點擊和生命周期,通過DraweeHolder 來控制其他兩個類的操作。
想想如果是你,你會抽出 DraweeHolder 這樣一個類嗎?實際上,這里對我們平時開發(fā)也是有所借鑒,嚴(yán)格控制每一個類之間的關(guān)系,可以引入一些一些中間類,讓類與類之間的關(guān)系耦合度降低,方便日后迭代。
具體引用關(guān)系如下圖:

它的主要功能是: 接收DraweeView的圖片加載請求,控制ProducerSequence發(fā)起圖片加載和處理流程,監(jiān)聽ProducerSequence加載過程中的事件(失敗、完成等),并更新最新的Drawable到DraweeHierachy。
五、DraweeController 的構(gòu)造邏輯
在Fresco中DraweeController是通過PipelineDraweeControllerBuilderSupplier 獲取的。Fresco在初始化時會調(diào)用下面的代碼:
// Fresco.java
private static void initializeDrawee(Context context, @Nullable DraweeConfig draweeConfig) {
sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}
sDraweeControllerBuilderSupplier 是靜態(tài)變量,也就是說其在只會初始一次。所有的DraweeController都是通過調(diào)用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(); // 調(diào)用一次就會創(chuàng)建一個新的實例
}
// ...... 省略其他代碼
}
Fresco每次圖片加載都會對應(yīng)到一個DraweeController,一個DraweeView的多次圖片加載可以復(fù)用同一個DraweeController:
SimpleDraweeView.java
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller =
mControllerBuilder
.setCallerContext(callerContext)
.setUri(uri) //設(shè)置新的圖片加載路徑
.setOldController(getController()) //復(fù)用 controller
.build();
setController(controller);
}
所以一般情況下 : 一個DraweeView對應(yīng)一個DraweeController。
六、通過 DataSource 發(fā)起圖片加載
在前面已經(jīng)說了DraweeController是直接持有DraweeHierachy,所以它觀察到ProducerSequence的數(shù)據(jù)變化是可以很容易更新到DraweeHierachy(具體代碼先不展示了)。那它是如何控制ProducerSequence來加載圖片的呢?其實DraweeController并不會直接和ProducerSequence發(fā)生關(guān)聯(lián)。對于圖片的加載,它直接接觸的是DataSource,由DataSource進(jìn)而來控制ProducerSequence發(fā)起圖片加載和處理流程。下面就跟隨源碼來看一下DraweeController是如果通過DataSource來控制ProducerSequence發(fā)起圖片加載和處理流程的。
// AbstractDraweeController.java
protected void submitRequest() {
mDataSource = getDataSource();
final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { //可以簡單的把它理解為一個監(jiān)聽者
@Override
public void onNewResultImpl(DataSource<T> dataSource) { //圖片加載成功
...
}
...
};
...
mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回調(diào)方法運行的線程,這里是主線程
}
那DataSource是什么呢? getDataSource()最終會調(diào)用到:
// 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。這個類在構(gòu)造的時候就會啟動圖片加載流程(它的構(gòu)造方法會調(diào)用producer.produceResults(...),這個方法就是圖片加載的起點,我們后面再看)。
這里我們總結(jié)一下Fresco中DataSource的概念以及作用:在Fresco中DraweeController每發(fā)起一次圖片加載就會創(chuàng)建一個DataSource,這個DataSource用來提供這次請求的數(shù)據(jù)(圖片)。DataSource只是一個接口,至于具體的加載流程Fresco是通過ProducerSequence來實現(xiàn)的。
七、Fresco圖片加載前的邏輯
了解了上面的知識后,我們過一遍圖片加載的源碼(從 UI 到 DraweeController),來理一下目前所了解的各個模塊之間的聯(lián)系。我們在使用Fresco加載圖片時一般是使用這個API:SimpleDraweeView.setImageURI(imageLink),這個方法最終會調(diào)用到:
// SimpleDraweeView.java
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller = mControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build(); //這里會復(fù)用 controller
setController(controller);
}
public void setController(@Nullable DraweeController draweeController) {
mDraweeHolder.setController(draweeController);
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
}
即每次加載都會使用DraweeControllerBuilder來build一個DraweeController。其實這個DraweeController默認(rèn)是復(fù)用的,這里的復(fù)用針對的是同一個SimpleDraweeView
。然后會把DraweeController設(shè)置給DraweeHolder,并在加載開始默認(rèn)是從DraweeHolder獲取TopLevelDrawable并展示到DraweeView。繼續(xù)看一下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;
// 注意這里是只有確定已經(jīng) attached 才會調(diào)用,也就是才回去加載圖片
if (wasAttached) {
attachController();
}
}
在DraweeHolder.setController()中把DraweeHierachy設(shè)置給DraweeController,并重新attachController(),attachController()主要調(diào)用了DraweeController.onAttach():
// AbstractDraweeController.java
public void onAttach() {
...
mIsAttached = true;
if (!mIsRequestSubmitted) {
submitRequest();
}
}
protected void submitRequest() {
mDataSource = getDataSource();
final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { //可以簡單的把它理解為一個監(jiān)聽者
@Override
public void onNewResultImpl(DataSource<T> dataSource) { //圖片加載成功
...
}
...
};
...
mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回調(diào)方法運行的線程,這里是主線程
}
即通過submitRequest()提交了一個請求,這個方法我們前面已經(jīng)看過了,它所做的主要事情就是,構(gòu)造了一個DataSource。這個DataSource我們經(jīng)過追蹤,它的實例實際上是CloseableProducerToDataSourceAdapter。CloseableProducerToDataSourceAdapter在構(gòu)造時就會調(diào)用producer.produceResults(...),進(jìn)而發(fā)起整個圖片加載流程。
用下面這張圖總結(jié)從SimpleDraweeView->DraweeController的圖片加載邏輯:

到這里我們梳理完了Fresco在真正發(fā)起圖片加載前所走的邏輯,那么Fresco的圖片加載流程是如何控制的呢?到底經(jīng)歷了哪些步驟呢?
八、Producer
Fresco中有關(guān)圖片的內(nèi)存緩存、解碼、編碼、磁盤緩存、網(wǎng)絡(luò)請求都是在這一層實現(xiàn)的,而所有的實現(xiàn)的基本單元是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);
}
結(jié)合注釋我們可以這樣定義Producer的作用:一個Producer用來處理整個Fresco圖片處理流程中的一步,比如從網(wǎng)絡(luò)獲取圖片、內(nèi)存獲取圖片、解碼圖片等等。而對于Consumer可以把它理解為監(jiān)聽者,看一下它的定義:
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的處理結(jié)果可以通過Consumer來告訴外界,比如是失敗還是成功。
九、Producer 的組合
一個ProducerA可以接收另一個ProducerB作為參數(shù),如果ProducerA處理完畢后可以調(diào)用ProducerB來繼續(xù)處理。并傳入Consumer來觀察ProducerB的處理結(jié)果。比如Fresco在加載圖片時會先去內(nèi)存緩存獲取,如果內(nèi)存緩存中沒有那么就網(wǎng)絡(luò)加載。這里涉及到兩個Producer分別是BitmapMemoryCacheProducer和NetworkFetchProducer,假設(shè)BitmapMemoryCacheProducer為ProducerA,NetworkFetchProducer為ProducerB。我們用偽代碼看一下他們的邏輯:
// BitmapMemoryCacheProducer.java
public class BitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> {
private final Producer<CloseableReference<CloseableImage>> mInputProducer;
// 我們假設(shè) 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,即mInputProducer產(chǎn)生結(jié)果時,它自己可以觀察到
Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(consumer..);
//網(wǎng)絡(luò)加載
mInputProducer.produceResults(wrappedConsumer, producerContext);
}
}
// NetworkFetchProducer.java
public class NetworkFetchProducer implements Producer<EncodedImage> {
// 它并沒有 inputProducer, 對于 Fresco 的圖片加載來說如果網(wǎng)絡(luò)都獲取失敗,那么就是圖片加載失敗了
@Override
public void produceResults(final Consumer<CloseableReference<CloseableImage>> consumer,..) {
// 網(wǎng)路獲取
// ...
if(獲取到網(wǎng)絡(luò)圖片){
notifyConsumer(...); //把結(jié)果通知給consumer,即觀察者
}
...
}
}
代碼可能不是很好理解,可以結(jié)合下面這張圖來理解這個關(guān)系:

Fresco可以通過組裝多個不同的Producer來靈活的定義不同的圖片處理流程的,多個Producer組裝在一塊稱為ProducerSequence (Fresco 中并沒有這個類哦)。一個ProducerSequence一般定義一種圖片處理流程,比如網(wǎng)絡(luò)加載圖片的ProducerSequence叫做NetworkFetchSequence,它包含多個不同類型的Producer。
十、網(wǎng)絡(luò)圖片加載的處理流程
在Fresco中不同的圖片請求會有不同的ProducerSequence來處理,比如網(wǎng)絡(luò)圖片請求:
// ProducerSequenceFactory.java
private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(ImageRequest imageRequest) {
switch (imageRequest.getSourceUriType()) {
case SOURCE_TYPE_NETWORK: return getNetworkFetchSequence();
...
}
所以對于網(wǎng)絡(luò)圖片請求會調(diào)用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會經(jīng)過重重調(diào)用來組合多個Producer。這里我就不追代碼邏輯了,直接用下面這張圖來描述Fresco網(wǎng)絡(luò)加載圖片的處理流程:

可以看到Fresco的整個圖片加載過程還是十分復(fù)雜的。并且上圖我只是羅列一些關(guān)鍵的Producer,其實還有一些我沒有畫出來。
十一、總結(jié)
為了輔助理解,再提供一張總結(jié)的流程圖,將上面整個過程都放在里面了。后續(xù)的系列文章會詳細(xì)介紹 UI 和圖片加載過程,希望通過閱讀其源碼來詳細(xì)了解內(nèi)部的代碼邏輯以及設(shè)計思路。

其實我們在閱讀別人源碼的時候,除了要知道具體的細(xì)節(jié)之外,也要注意別人的模塊設(shè)計,借鑒其設(shè)計思想。然后想想如果是你在設(shè)計的時候,你會怎么劃分模塊,如何將不同的模塊聯(lián)系起來。
當(dāng)模塊劃分后,里面的子模塊又是如何劃分的,它們之間協(xié)作關(guān)系如何保持。
以上就是詳細(xì)分析Fresco源碼之圖片加載流程的詳細(xì)內(nèi)容,更多關(guān)于Fresco 圖片加載流程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android Studio 導(dǎo)入開源項目的正確姿勢及注意事項
這篇文章主要介紹了Android Studio 導(dǎo)入開源項目的正確姿勢及注意事項,需要的朋友參考下吧2018-03-03
Android自定義DataTimePicker實例代碼(日期選擇器)
本篇文章主要介紹了Android自定義DataTimePicker實例代碼(日期選擇器),非常具有實用價值,需要的朋友可以參考下。2017-01-01
Android使用Activity實現(xiàn)簡單的可輸入對話框
大家在做彈出對話框效果的時候最容易想到的是用Dialog顯示,但其實彈出對話框的實現(xiàn)效果有兩種:Dialog和Activity,那么下面這篇文章就來給大家介紹了關(guān)于Android使用Activity如何實現(xiàn)一個簡單的可輸入對話框的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10
Android?拍照功能實現(xiàn)(手機關(guān)閉依然拍照)詳解及實例代碼
這篇文章主要介紹了?Android?拍照功能實現(xiàn)(手機關(guān)閉依然拍照)詳解及實例代碼的相關(guān)資料,這對Android相機在不開手機的情況下還能繼續(xù)拍照,附有實例Demo,需要的朋友可以參考下2016-12-12
android 九宮格滑動解鎖開機實例源碼學(xué)習(xí)
開機密碼的樣式種類多種多樣,五花八門.本文接下來介紹滑動九宮格來達(dá)到開機目的,感興趣的朋友可以了解下2013-01-01
android播放視頻時在立體聲與單聲道之間切換無變化原因分析及解決
使用第三方視頻播放器,有立體聲與單聲道之間切換,發(fā)現(xiàn)切換后無作用,原因是由于在HAL層默認(rèn)沒有處理上層發(fā)的stereo 轉(zhuǎn)mono的命令,具體的解決方法如下2013-06-06

