如果你是第一次看我的Fresco的原始碼分析系列文章,這裡強烈推薦你先閱讀我的前面兩篇文章Fresco原始碼分析之DraweeView與Fresco原始碼分析之Hierarchy。好了,下面進入正題。在上篇文章中我們提到,在Fresco中關於圖片的快取、請求與顯示邏輯處理都在Controller中。那麼Controller到底是如何貫穿這些功能的呢?我們先從它的出生開始。
Suppiler
PipelineDraweeControllerBuilderSupplier是一個供應商,主要實現了Supplier<T>
介面,它只有一個方法T get(),用來獲取相關的提供實現。因此該供應類提供的就是PipelineDraweeControllerBuilder例項。
@Override
public PipelineDraweeControllerBuilder get() {
return new PipelineDraweeControllerBuilder(
mContext,
mPipelineDraweeControllerFactory,
mImagePipeline,
mBoundControllerListeners);
}
複製程式碼
在生成的builder中有4個引數,第一個是Context再熟悉不過了;第二個是Controller的工廠;第三個是資料管道ImagePipeline;第四個是listener的set集合,主要用在圖片請求之後的監聽回撥。下面詳細說明後面三個引數內容與作用。
PipelineDraweeControllerFactory
在這個類中主要就兩個方法,分別為internalCreateController與newController,對外的方法就一個newController。這個兩個方法都是用來建立PipelineDraweeController物件。其中newController內部就是呼叫了internalCreateController來進行建立PipelineDraweeController例項。
protected PipelineDraweeController internalCreateController(
Resources resources,
DeferredReleaser deferredReleaser,
DrawableFactory animatedDrawableFactory,
Executor uiThreadExecutor,
MemoryCache<CacheKey, CloseableImage> memoryCache,
@Nullable ImmutableList<DrawableFactory> globalDrawableFactories,
@Nullable ImmutableList<DrawableFactory> customDrawableFactories,
Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier,
String id,
CacheKey cacheKey,
Object callerContext) {
PipelineDraweeController controller = new PipelineDraweeController(
resources,
deferredReleaser,
animatedDrawableFactory,
uiThreadExecutor,
memoryCache,
dataSourceSupplier,
id,
cacheKey,
callerContext,
globalDrawableFactories);
controller.setCustomDrawableFactories(customDrawableFactories);
return controller;
}
複製程式碼
其中DeferredReleaser是用來管理推遲資源釋放的。我們在之前的文章已經提到,在onAttach中會進行載入資源,而onDetach中又會釋放資源。因為在Fresco中往往會在onDetach與onAttach之間頻繁切換(view的顯隱、繪製與Controller的設定都會呼叫),並且它們都處於在同一個looper(其實就是主程式的looper)中。如果在onDetach時馬上釋放資源的話,這樣會造成資源的濫用,導致不必要的資源載入與釋放回收。所以就用了這個資源推遲釋放的機制(內部原理是使用了set集合的唯一性的特性)。
dataSourceSupplier是DataSource的供應商,用來提供DataSource例項。而DataSource是用來獲取與儲存請求結果的,相當與圖片資料來源。這些都會在後續的Controller中使用到。
ImagePipeline
既然它是資料管道,自然是與網路請求與快取資料有關。其實我們可以把它理解為多個管道的集合,最終顯示的圖片資源就是來自於它們中的其中一個。下面介紹其中的主要方法:
- fetchDecodedImage() 傳送請求,返回decode image的資料來源。
- fetchEncodedImage() 傳送請求,返回encoded image的資料來源。
- prefetchToBitmapCache() 傳送預處理請求,獲取預處理的bitmap快取資料。
- prefetchToDiskCache() 傳送預處理請求,獲取預處理的磁碟快取資料。
- submitFetchRequest() 傳送請求,獲取相應型別的資料來源。
- submitPrefetchRequest() 傳送預處理請求,獲取相應型別的快取資料。
這裡用的最多的還是fetchDecodedImage()
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
ImageRequest imageRequest,
Object callerContext,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit) {
try {
Producer<CloseableReference<CloseableImage>> producerSequence =
mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
return submitFetchRequest(
producerSequence,
imageRequest,
lowestPermittedRequestLevelOnSubmit,
callerContext);
} catch (Exception exception) {
return DataSources.immediateFailedDataSource(exception);
}
}
複製程式碼
這裡主要涉及到Producer,這是一個生產者,內部只有一個公共介面方法void produceResults(Consumer consumer, ProducerContext context),用來獲取資料來源。其實我們會發現submitFetchRequest方法中的producer傳入的其實是一個佇列,因為內部會遞迴呼叫*produceResults()*來獲取最終的資料來源。
關於Producer後續有時間的話會單獨開篇文章詳細分析。
ControllerListener
如果你對ControllerListener不熟悉的話,那麼BaseControllerListener應該或多或少使用過吧。它其實就是ControllerListener的空實現。既然是監聽回撥,那麼來看下它提供的回撥方法與呼叫時機。
- void onSubmit(String id, Object callerContext) 在傳送請求的時候回撥
- void onFinalImageSet(String id, @Nullable INFO imageInfo, @Nullable Animatable animatable) 在最終設定image圖片時回撥,其中imageInfo包含圖片的相關基本資訊(width、height與quality)
- void onIntermediateImageSet(String id, @Nullable INFO imageInfo) 在傳送請求與最終圖片設定的過程中回撥
- void onIntermediateImageFailed(String id, Throwable throwable) 在傳送請求與最終失敗的過程中回撥
- void onFailure(String id, Throwable throwable) 傳送請求失敗時回撥
- void onRelease(String id) 資源釋放時回撥
ControllerBuilder
既然是builder模式,最終的目的自然就是用來建立Controller,所以我們可以直接奔著它的目的來分析。在這裡Controller的builder類是PipelineDraweeControllerBuilder。我們找到它的*build()發現在它的父類AbstractDraweeControllerBuilder中。但最終的建立例項方法還是呼叫了obtainController()*抽象方法。所以經過反轉還是回到了PipelineDraweeControllerBuilder,那麼我們直接來看下它建立方式。
@Override
protected PipelineDraweeController obtainController() {
DraweeController oldController = getOldController();
PipelineDraweeController controller;
if (oldController instanceof PipelineDraweeController) {
controller = (PipelineDraweeController) oldController;
controller.initialize(
obtainDataSourceSupplier(),
generateUniqueControllerId(),
getCacheKey(),
getCallerContext(),
mCustomDrawableFactories);
} else {
controller = mPipelineDraweeControllerFactory.newController(
obtainDataSourceSupplier(),
generateUniqueControllerId(),
getCacheKey(),
getCallerContext(),
mCustomDrawableFactories);
}
return controller;
}
複製程式碼
通過上面的程式碼,邏輯已經很明顯了。首先判斷是否已經存在Controller,如果存在的話就無需建立新的例項,只需呼叫initialize()方法進行重寫初始化;如果不存在,那麼就呼叫我們文章之前分析的PipelineDraweeControllerFactory中的newController()來建立新的例項。這裡主要的引數還是obtainDataSourceSupplier(),之前也簡單提到了,它是DataSource的供應者。那麼我們來看下Supplier的建立
protected Supplier<DataSource<IMAGE>> getDataSourceSupplierForRequest(
final REQUEST imageRequest,
final CacheLevel cacheLevel) {
final Object callerContext = getCallerContext();
return new Supplier<DataSource<IMAGE>>() {
@Override
public DataSource<IMAGE> get() {
return getDataSourceForRequest(imageRequest, callerContext, cacheLevel);
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("request", imageRequest.toString())
.toString();
}
};
}
複製程式碼
在這個方法中,我們一眼就看到了Supplier的建立,之前也提到它只有一個*get()方法,就是用來提供所以需要的DataSource。在這裡也是如此,這裡它呼叫了getDataSourceForRequest()方法,該方法是一個抽象方法,細節實現由它的子類實現,所以我們可以再次回到getDataSourceForRequest,在其中就能夠搜尋到getDataSourceForRequest()*方法
@Override
protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(
ImageRequest imageRequest,
Object callerContext,
CacheLevel cacheLevel) {
return mImagePipeline.fetchDecodedImage(
imageRequest,
callerContext,
convertCacheLevelToRequestLevel(cacheLevel));
}
複製程式碼
看到上面的方法實現方法是否眼熟呢?這也是我們上面所提到的ImagePipleline中的方法,這裡就不在多做分析了。這樣Controller就與獲取資料的通道建立了聯絡。那麼下面我們就轉戰到Controller中,看看它到底做了什麼。
Controller
PipelineDraweeController繼承於AbstractDraweeController,在PipelineDraweeController中主要的方法有三個
- Drawable createDrawable(CloseableImage closeableImage) 這是內部類DrawableFactory中的方法,是一個工廠,不言而喻它是用來建立Drawable的,在資料來源返回的時候回撥,進而顯示到Hierarchy層。
- getDataSource() 獲取資料來源通道,與其建立聯絡。
- void setHierarchy(@Nullable DraweeHierarchy hierarchy) 設定Hierarchy圖層,內部持有的其實是SettableDraweeHierarchy介面物件。所以內部呼叫的也就是它的6個介面方法。之前的文章也有提及,用來控制圖片載入過程中的顯示邏輯。
其餘的邏輯處理都在它的父類AbstractDraweeController中。在之前我們多次提及到onAttach與onDetach方法,它們分別是處理資料載入與釋放。
onAttach
@Override
public void onAttach() {
if (FLog.isLoggable(FLog.VERBOSE)) {
FLog.v(
TAG,
"controller %x %s: onAttach: %s",
System.identityHashCode(this),
mId,
mIsRequestSubmitted ? "request already submitted" : "request needs submit");
}
//事件記錄器
mEventTracker.recordEvent(Event.ON_ATTACH_CONTROLLER);
Preconditions.checkNotNull(mSettableDraweeHierarchy);
//取消資源推遲釋放機制,防止資源被釋放
mDeferredReleaser.cancelDeferredRelease(this);
mIsAttached = true;
if (!mIsRequestSubmitted) {
submitRequest();
}
}
複製程式碼
在這個方法中mEventTracker是事件記錄器,預設是開啟的,如果要關閉則需要在*Fresco.initialize()之前呼叫DraweeEventTracker.disable()關閉;然後就是將其從資源推遲釋放機制中取消;最後就是呼叫submitRequest()*傳送資料來源請求。
protected void submitRequest() {
final T closeableImage = getCachedImage();
//1.判斷記憶體快取中是否存在
if (closeableImage != null) {
mDataSource = null;
mIsRequestSubmitted = true;
mHasFetchFailed = false;
mEventTracker.recordEvent(Event.ON_SUBMIT_CACHE_HIT);
//1.1資料獲取中通知回撥
getControllerListener().onSubmit(mId, mCallerContext);
//1.2資料處理
onNewResultInternal(mId, mDataSource, closeableImage, 1.0f, true, true);
return;
}
mEventTracker.recordEvent(Event.ON_DATASOURCE_SUBMIT);
//2.通過DataSource獲取資料來源
//2.1資料獲取中通知回撥
getControllerListener().onSubmit(mId, mCallerContext);
mSettableDraweeHierarchy.setProgress(0, true);
mIsRequestSubmitted = true;
mHasFetchFailed = false;
mDataSource = getDataSource();
if (FLog.isLoggable(FLog.VERBOSE)) {
FLog.v(
TAG,
"controller %x %s: submitRequest: dataSource: %x",
System.identityHashCode(this),
mId,
System.identityHashCode(mDataSource));
}
final String id = mId;
final boolean wasImmediate = mDataSource.hasResult();
//內部請求資料回撥,當資料來源返回時回撥
final DataSubscriber<T> dataSubscriber =
new BaseDataSubscriber<T>() {
@Override
public void onNewResultImpl(DataSource<T> dataSource) {
// isFinished must be obtained before image, otherwise we might set intermediate result
// as final image.
boolean isFinished = dataSource.isFinished();
float progress = dataSource.getProgress();
T image = dataSource.getResult();
//2.2資料處理
if (image != null) {
//成功處理
onNewResultInternal(id, dataSource, image, progress, isFinished, wasImmediate);
} else if (isFinished) {
//失敗處理
onFailureInternal(id, dataSource, new NullPointerException(), /* isFinished */ true);
}
}
@Override
public void onFailureImpl(DataSource<T> dataSource) {
//失敗處理
onFailureInternal(id, dataSource, dataSource.getFailureCause(), /* isFinished */ true);
}
@Override
public void onProgressUpdate(DataSource<T> dataSource) {
boolean isFinished = dataSource.isFinished();
float progress = dataSource.getProgress();
//資料進度處理
onProgressUpdateInternal(id, dataSource, progress, isFinished);
}
};
//資料來源訂閱回撥註冊
mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor);
}
複製程式碼
邏輯方面的處理,上面程式碼中已經詳細註釋了,總的來說就是先從記憶體中獲取如果存在就直接拿來用,否則就通過DataSource從網路或者是本地資源中獲取。使用DataSource方式會使用到DataSubscriber,即訂閱方式。當資料來源已經獲取到時,傳送通知給訂閱者,因此分別回撥訂閱者的方法。上述兩種方式只要成功了都會交由*onNewResultInternal()處理,而失敗則由onFailureInternal()處理,同時請求進度處理由onProgressUpdateInternal()*處理。
private void onNewResultInternal(
String id,
DataSource<T> dataSource,
@Nullable T image,
float progress,
boolean isFinished,
boolean wasImmediate) {
// ignore late callbacks (data source that returned the new result is not the one we expected)
if (!isExpectedDataSource(id, dataSource)) {
logMessageAndImage("ignore_old_datasource @ onNewResult", image);
releaseImage(image);
dataSource.close();
return;
}
mEventTracker.recordEvent(
isFinished ? Event.ON_DATASOURCE_RESULT : Event.ON_DATASOURCE_RESULT_INT);
// create drawable
Drawable drawable;
try {
drawable = createDrawable(image);
} catch (Exception exception) {
logMessageAndImage("drawable_failed @ onNewResult", image);
releaseImage(image);
onFailureInternal(id, dataSource, exception, isFinished);
return;
}
T previousImage = mFetchedImage;
Drawable previousDrawable = mDrawable;
mFetchedImage = image;
mDrawable = drawable;
try {
// set the new image
if (isFinished) {
logMessageAndImage("set_final_result @ onNewResult", image);
mDataSource = null;
//通過hierarchy(GenericDraweeHierarchy)來設定image
mSettableDraweeHierarchy.setImage(drawable, 1f, wasImmediate);
getControllerListener().onFinalImageSet(id, getImageInfo(image), getAnimatable());
// IMPORTANT: do not execute any instance-specific code after this point
} else {
logMessageAndImage("set_intermediate_result @ onNewResult", image);
mSettableDraweeHierarchy.setImage(drawable, progress, wasImmediate);
getControllerListener().onIntermediateImageSet(id, getImageInfo(image));
// IMPORTANT: do not execute any instance-specific code after this point
}
} finally {
if (previousDrawable != null && previousDrawable != drawable) {
releaseDrawable(previousDrawable);
}
if (previousImage != null && previousImage != image) {
logMessageAndImage("release_previous_result @ onNewResult", previousImage);
releaseImage(previousImage);
}
}
}
複製程式碼
這裡我們主要就看裡面的兩個try。
第一個使用createDrawable(image)將拿到的資料來源轉變成Drawable,這個方法的具體實現是在子類中實現(上面也有提及)。
第二個分為兩種情況,一方面如果資料來源已經全部獲取完,則直接呼叫SettableDraweeHierarchy介面的setImage()方法將圖片設定到Hierarchy圖層上,同時呼叫Listener的回撥方法onFinalImageSet();另一方面如果資料來源還在獲取中,也是呼叫SettableDraweeHierarchy介面的*setImage()方法,只是其中的引數progress根據進度來設定而已,由於還處於資源獲取中所以呼叫onIntermediateImageSet()*回撥。
這樣Controller就與Hierarchy聯絡起來了,將需要的圖片設定到顯示的圖片中。
對於SettableDraweeHierarchy中的這些方法如果不理解的可以回過頭去看我之前的這篇文章Fresco原始碼分析之Hierarchy
由於原始碼太多,對於*onFailureInternal()與onProgressUpdateInternal()*這裡就不貼出原始碼來進行分析了。原理與呼叫的方法基本類似,如果想看原始碼的可以點這裡
onDetach
@Override
public void onDetach() {
if (FLog.isLoggable(FLog.VERBOSE)) {
FLog.v(TAG, "controller %x %s: onDetach", System.identityHashCode(this), mId);
}
mEventTracker.recordEvent(Event.ON_DETACH_CONTROLLER);
mIsAttached = false;
mDeferredReleaser.scheduleDeferredRelease(this);
}
複製程式碼
相對於之前的分析*onDetach()*就簡單多了,這裡它只是對資源進行釋放,釋放的策略也是推遲釋放策略DeferredReleaser。
End
本篇文章主要分析了Fresco中的Controller相關處理邏輯,它控制著Hierarchy顯示邏輯,同時它是資料來源的獲取橋樑通過DataSource來連結資料來源的獲取。那麼問題又來了,DataSource又是如何產生的呢?同時它的內部邏輯又是如何的呢?這就涉及到Producer了,敬請關注下篇文章Fresco原始碼分析之Producer