本文是
Fresco
原始碼分析系列的開篇,主要分析Fresco
的整體架構、各個組成模組的功能以及圖片載入流程,希望通過本文可以對Fresco
的整體框架設計有一個大概的瞭解,也為後續更為深入的分析打下基礎。
Fresco
原始碼龐大,涉及的圖片載入情況眾多。本系列Fresco
原始碼分析是沿著Fresco網路載入圖片這個點展開的。
Fresco的整體架構
Fresco
的組成結構還是比較清晰的,大致如下圖所示:
下面結合程式碼分別解釋一下上面各模組的作用以及大概的工作原理。
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個類之間的粘合劑,具體引用關係如下圖:
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
的圖片載入邏輯:
到這裡我們梳理完了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,即觀察者
}
...
}
}
複製程式碼
程式碼可能不是很好理解,可以結合下面這張圖來理解這個關係:
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
,其實還有一些我沒有畫出來,有興趣可以去原始碼細細探討一下。
OK,到這裡本文算是結束了,希望你可以通過本文對Fresco
的設計在整體上有一定的瞭解。後續文章會繼續討論Fresco
的快取邏輯、圖片壓縮、DraweeHierachy
的Drawable
切換邏輯等。歡迎繼續關注。
目前我在小紅書工作, 如果您有興趣加入小紅書客戶端團隊,歡迎投遞簡歷至pwang1@xiaohongshu.com
歡迎關注我的Android進階計劃看更多幹貨
歡迎關注我的微信公眾號:susion隨心