詳細(xì)分析Fresco源碼之圖片加載流程
一、概述
Fresco 是一個(gè)強(qiáng)大的圖片加載組件。使用它之后,你不需要再去關(guān)心圖片的加載和顯示這些繁瑣的事情! 支持 Android 2.3 及以后的版本。如果需要了解 Fresco 的使用可以訪問 Fresco 使用文檔。
Fresco是一個(gè)功能完善的圖片加載框架,在Android開發(fā)中有著廣泛的應(yīng)用,那么它作為一個(gè)圖片加載框架,有哪些特色讓它備受推崇呢?
- 完善的內(nèi)存管理功能,減少圖片對(duì)內(nèi)存的占用,即便在低端機(jī)器上也有著不錯(cuò)的表現(xiàn)。
- 自定義圖片加載的過程,可以先顯示低清晰度圖片或者縮略圖,加載完成后再顯示高清圖,可以在加載的時(shí)候縮放和旋轉(zhuǎn)圖片。
- 自定義圖片繪制的過程,可以自定義谷中焦點(diǎn)、圓角圖、占位圖、overlay、進(jìn)圖條。
- 漸進(jìn)式顯示圖片。
- 支持Gif。
- 支持Webp。
Fresco
的組成結(jié)構(gòu)還是比較清晰的,大致如下圖所示:
其實(shí)這兩張圖來自不同的文章,但是我覺得兩者的分層實(shí)際上基本是一樣的。只是一個(gè)比較概括,一個(gè)比價(jià)具體,將兩者擺在一起,更有助于大家去理解其實(shí)現(xiàn)細(xì)節(jié)。當(dāng)然除了 UI 和加載顯示部分外,還有 Gif,動(dòng)態(tài)圖片等內(nèi)容,以及對(duì)應(yīng)圖片解碼編碼邏輯等。這部分不打算去講解,因?yàn)檫@部分雖然也是源碼很重要的一部分,但是這部分需要相關(guān)專業(yè)知識(shí)才好說明白,此外且涉及到 C++ 代碼。
下面結(jié)合代碼分別解釋一下上面各模塊的作用以及大概的工作原理。
二、DraweeView
它繼承自ImageView,
是Fresco
加載圖片各個(gè)階段過程中圖片顯示的載體,比如在加載圖片過程中它顯示的是占位圖、在加載成功時(shí)切換為目標(biāo)圖片。不過后續(xù)官方可能不再讓這個(gè)類繼承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()
會(huì)在Fresco
加載圖片時(shí)會(huì)調(diào)用。其實(shí)在這里可以看出Fresco
的圖片顯示原理是 :利用ImageView
顯示DraweeHierachy
的TopLevelDrawable
。上面這段代碼引出了UI 層
中另外兩個(gè)關(guān)鍵類:DraweeHolder
和DraweeHierachy
。
三、DraweeHierachy
可以說它是Fresco
圖片顯示的實(shí)現(xiàn)者。它的輸出是Drawable
,這個(gè)Drawable
會(huì)被DraweeView
拿來顯示(上面已經(jīng)說了)。它內(nèi)部有多個(gè)Drawable
,當(dāng)前顯示在DraweeView
的Drawable
叫做TopLevelDrawable
。在不同的圖片加載階段,TopLevelDrawable
是不同的(比如加載過程中是 placeholder,加載完成是目標(biāo)圖片)。具體的Drawable
切換邏輯是由它來具體實(shí)現(xiàn)的。
它是由DraweeController
直接持有的,因此對(duì)于不同圖片顯示的切換操作具體是由DraweeController
來直接操作的。
四、DraweeHolder
可以把它理解為DraweeView
、DraweeHierachy
和DraweeController
這 3 個(gè)類之間的粘合劑, DraweeView 并不直接和DraweeController 和DraweeHierachy 直接接觸,所有的操作都是通過它傳過去。這樣,后續(xù)將 DraweeView 的父類改為 View,也不會(huì)影響到其他類。DraweeView 作為 View 可以感知點(diǎn)擊和生命周期,通過DraweeHolder 來控制其他兩個(gè)類的操作。
想想如果是你,你會(huì)抽出 DraweeHolder 這樣一個(gè)類嗎?實(shí)際上,這里對(duì)我們平時(shí)開發(fā)也是有所借鑒,嚴(yán)格控制每一個(gè)類之間的關(guān)系,可以引入一些一些中間類,讓類與類之間的關(guān)系耦合度降低,方便日后迭代。
具體引用關(guān)系如下圖:
它的主要功能是: 接收DraweeView
的圖片加載請(qǐng)求,控制ProducerSequence
發(fā)起圖片加載和處理流程,監(jiān)聽ProducerSequence
加載過程中的事件(失敗、完成等),并更新最新的Drawable
到DraweeHierachy
。
五、DraweeController 的構(gòu)造邏輯
在Fresco
中DraweeController
是通過PipelineDraweeControllerBuilderSupplier 獲取的。Fresco
在初始化時(shí)會(huì)調(diào)用下面的代碼:
// Fresco.java private static void initializeDrawee(Context context, @Nullable DraweeConfig draweeConfig) { sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context, draweeConfig); SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier); }
sDraweeControllerBuilderSupplier 是靜態(tài)變量,也就是說其在只會(huì)初始一次。所有的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)用一次就會(huì)創(chuàng)建一個(gè)新的實(shí)例 } // ...... 省略其他代碼 }
Fresco
每次圖片加載都會(huì)對(duì)應(yīng)到一個(gè)DraweeController
,一個(gè)DraweeView
的多次圖片加載可以復(fù)用同一個(gè)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); }
所以一般情況下 : 一個(gè)DraweeView
對(duì)應(yīng)一個(gè)DraweeController
。
六、通過 DataSource 發(fā)起圖片加載
在前面已經(jīng)說了DraweeController
是直接持有DraweeHierachy
,所以它觀察到ProducerSequence
的數(shù)據(jù)變化是可以很容易更新到DraweeHierachy
(具體代碼先不展示了)。那它是如何控制ProducerSequence
來加載圖片的呢?其實(shí)DraweeController
并不會(huì)直接和ProducerSequence
發(fā)生關(guān)聯(lián)。對(duì)于圖片的加載,它直接接觸的是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)單的把它理解為一個(gè)監(jiān)聽者 @Override public void onNewResultImpl(DataSource<T> dataSource) { //圖片加載成功 ... } ... }; ... mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回調(diào)方法運(yùn)行的線程,這里是主線程 }
那DataSource
是什么呢? getDataSource()
最終會(huì)調(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è)類在構(gòu)造的時(shí)候就會(huì)啟動(dòng)圖片加載流程(它的構(gòu)造方法會(huì)調(diào)用producer.produceResults(...),
這個(gè)方法就是圖片加載的起點(diǎn),我們后面再看)。
這里我們總結(jié)一下Fresco
中DataSource
的概念以及作用:在Fresco
中DraweeController
每發(fā)起一次圖片加載就會(huì)創(chuàng)建一個(gè)DataSource,
這個(gè)DataSource
用來提供這次請(qǐng)求的數(shù)據(jù)(圖片)。DataSource
只是一個(gè)接口,至于具體的加載流程Fresco
是通過ProducerSequence
來實(shí)現(xiàn)的。
七、Fresco圖片加載前的邏輯
了解了上面的知識(shí)后,我們過一遍圖片加載的源碼(從 UI 到 DraweeController
),來理一下目前所了解的各個(gè)模塊之間的聯(lián)系。我們?cè)谑褂?code>Fresco加載圖片時(shí)一般是使用這個(gè)API:SimpleDraweeView.setImageURI(imageLink),
這個(gè)方法最終會(huì)調(diào)用到:
// SimpleDraweeView.java public void setImageURI(Uri uri, @Nullable Object callerContext) { DraweeController controller = mControllerBuilder .setCallerContext(callerContext) .setUri(uri) .setOldController(getController()) .build(); //這里會(huì)復(fù)用 controller setController(controller); } public void setController(@Nullable DraweeController draweeController) { mDraweeHolder.setController(draweeController); super.setImageDrawable(mDraweeHolder.getTopLevelDrawable()); }
即每次加載都會(huì)使用DraweeControllerBuilder
來build
一個(gè)DraweeController
。其實(shí)這個(gè)DraweeController
默認(rèn)是復(fù)用的,這里的復(fù)用針對(duì)的是同一個(gè)SimpleDraweeView
。然后會(huì)把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 才會(huì)調(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)單的把它理解為一個(gè)監(jiān)聽者 @Override public void onNewResultImpl(DataSource<T> dataSource) { //圖片加載成功 ... } ... }; ... mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回調(diào)方法運(yùn)行的線程,這里是主線程 }
即通過submitRequest()
提交了一個(gè)請(qǐng)求,這個(gè)方法我們前面已經(jīng)看過了,它所做的主要事情就是,構(gòu)造了一個(gè)DataSource
。這個(gè)DataSource
我們經(jīng)過追蹤,它的實(shí)例實(shí)際上是CloseableProducerToDataSourceAdapter
。CloseableProducerToDataSourceAdapter
在構(gòu)造時(shí)就會(huì)調(diào)用producer.produceResults(...),
進(jìn)而發(fā)起整個(gè)圖片加載流程。
用下面這張圖總結(jié)從SimpleDraweeView
->DraweeController
的圖片加載邏輯:
到這里我們梳理完了Fresco
在真正發(fā)起圖片加載前所走的邏輯,那么Fresco
的圖片加載流程是如何控制的呢?到底經(jīng)歷了哪些步驟呢?
八、Producer
Fresco
中有關(guān)圖片的內(nèi)存緩存、解碼、編碼、磁盤緩存、網(wǎng)絡(luò)請(qǐng)求都是在這一層實(shí)現(xiàn)的,而所有的實(shí)現(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
的作用:一個(gè)Producer
用來處理整個(gè)Fresco
圖片處理流程中的一步,比如從網(wǎng)絡(luò)獲取圖片、內(nèi)存獲取圖片、解碼圖片等等。而對(duì)于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 的組合
一個(gè)ProducerA
可以接收另一個(gè)ProducerB
作為參數(shù),如果ProducerA
處理完畢后可以調(diào)用ProducerB
來繼續(xù)處理。并傳入Consumer
來觀察ProducerB
的處理結(jié)果。比如Fresco
在加載圖片時(shí)會(huì)先去內(nèi)存緩存獲取,如果內(nèi)存緩存中沒有那么就網(wǎng)絡(luò)加載。這里涉及到兩個(gè)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é)果時(shí),它自己可以觀察到 Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(consumer..); //網(wǎng)絡(luò)加載 mInputProducer.produceResults(wrappedConsumer, producerContext); } }
// NetworkFetchProducer.java public class NetworkFetchProducer implements Producer<EncodedImage> { // 它并沒有 inputProducer, 對(duì)于 Fresco 的圖片加載來說如果網(wǎng)絡(luò)都獲取失敗,那么就是圖片加載失敗了 @Override public void produceResults(final Consumer<CloseableReference<CloseableImage>> consumer,..) { // 網(wǎng)路獲取 // ... if(獲取到網(wǎng)絡(luò)圖片){ notifyConsumer(...); //把結(jié)果通知給consumer,即觀察者 } ... } }
代碼可能不是很好理解,可以結(jié)合下面這張圖來理解這個(gè)關(guān)系:
Fresco
可以通過組裝多個(gè)不同的Producer
來靈活的定義不同的圖片處理流程的,多個(gè)Producer
組裝在一塊稱為ProducerSequence (Fresco 中并沒有這個(gè)類哦)
。一個(gè)ProducerSequence
一般定義一種圖片處理流程,比如網(wǎng)絡(luò)加載圖片的ProducerSequence
叫做NetworkFetchSequence,
它包含多個(gè)不同類型的Producer
。
十、網(wǎng)絡(luò)圖片加載的處理流程
在Fresco
中不同的圖片請(qǐng)求會(huì)有不同的ProducerSequence
來處理,比如網(wǎng)絡(luò)圖片請(qǐng)求:
// ProducerSequenceFactory.java private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(ImageRequest imageRequest) { switch (imageRequest.getSourceUriType()) { case SOURCE_TYPE_NETWORK: return getNetworkFetchSequence(); ... }
所以對(duì)于網(wǎng)絡(luò)圖片請(qǐng)求會(huì)調(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
會(huì)經(jīng)過重重調(diào)用來組合多個(gè)Producer
。這里我就不追代碼邏輯了,直接用下面這張圖來描述Fresco
網(wǎng)絡(luò)加載圖片的處理流程:
可以看到Fresco
的整個(gè)圖片加載過程還是十分復(fù)雜的。并且上圖我只是羅列一些關(guān)鍵的Producer,
其實(shí)還有一些我沒有畫出來。
十一、總結(jié)
為了輔助理解,再提供一張總結(jié)的流程圖,將上面整個(gè)過程都放在里面了。后續(xù)的系列文章會(huì)詳細(xì)介紹 UI 和圖片加載過程,希望通過閱讀其源碼來詳細(xì)了解內(nèi)部的代碼邏輯以及設(shè)計(jì)思路。
其實(shí)我們?cè)陂喿x別人源碼的時(shí)候,除了要知道具體的細(xì)節(jié)之外,也要注意別人的模塊設(shè)計(jì),借鑒其設(shè)計(jì)思想。然后想想如果是你在設(shè)計(jì)的時(shí)候,你會(huì)怎么劃分模塊,如何將不同的模塊聯(lián)系起來。
當(dāng)模塊劃分后,里面的子模塊又是如何劃分的,它們之間協(xié)作關(guān)系如何保持。
以上就是詳細(xì)分析Fresco源碼之圖片加載流程的詳細(xì)內(nèi)容,更多關(guān)于Fresco 圖片加載流程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android編程實(shí)現(xiàn)手繪及保存為圖片的方法(附demo源碼下載)
這篇文章主要介紹了Android編程實(shí)現(xiàn)手繪及保存為圖片的方法,涉及Android畫布的使用及圖片的操作技巧,并附帶了demo源碼供讀者下載,需要的朋友可以參考下2015-12-12Android開發(fā)之Parcel機(jī)制實(shí)例分析
這篇文章主要介紹了Android開發(fā)之Parcel機(jī)制,實(shí)例分析了Parcel機(jī)制的原理與實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-05-05Android開發(fā)中通過手機(jī)號(hào)+短信驗(yàn)證碼登錄的實(shí)例代碼
最近在開發(fā)一個(gè)android的項(xiàng)目,需要通過獲取手機(jī)驗(yàn)證碼來完成登錄功能,接下來通過實(shí)例代碼給大家分享手機(jī)號(hào)+短信驗(yàn)證碼登錄的實(shí)現(xiàn)方法,需要的的朋友參考下吧2017-05-05flutter中的網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)獲取詳解
這篇文章主要為大家介紹了flutter中的網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)獲取示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Android自定義ViewGroup(側(cè)滑菜單)詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android自定義ViewGroup(側(cè)滑菜單)詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02