文章目錄
- 一 圖片加載流程
- 1.1 初始化Fresco
- 1.2 獲取DataSource
- 1.3 綁定DraweeController與DraweeHierarchy
- 1.4 從內存緩存/磁盤緩存/網絡獲取圖片,並設置到對應的Drawable層
- 二 DraweeController與DraweeHierarchy
- 2.1 圖層的層級構造
- 2.2 圖層的構建流程
- 三 Producer與Consumer
- 四 緩存機制
- 3.1 內存緩存
- 3.2 磁盤緩存
更多Android開源框架源碼分析文章請參見 Android open framework analysis 。
這個系列的文章原來叫做《Android開源框架源碼分析》,後來這些優秀開源庫的代碼看的多了,感覺大佬們代碼寫的真真美如畫:+1:,所以就更名爲《Android開源框架源碼鑑賞》了。閒話 不多說,我們進入正題,今天分析的開源庫是Fresco。
Fresco是一個功能完善的圖片加載框架,在Android開發中有着廣泛的應用,那麼它作爲一個圖片加載框架,有哪些特色讓它備受推崇呢?
- 完善的內存管理功能,減少圖片對內存的佔用,即便在低端機器上也有着不錯的表現。
- 自定義圖片加載的過程,可以先顯示低清晰度圖片或者縮略圖,加載完成後再顯示高清圖,可以在加載的時候縮放和旋轉圖片。
- 自定義圖片繪製的過程,可以自定義谷中焦點、圓角圖、佔位圖、overlay、進圖條。
- 漸進式顯示圖片。
- 支持Gif。
- 支持Webp。
好,又吹了一波Fresco(人家好像也不給廣告費T_T),但是光知道人家好並沒有用,我們還需要爲什麼這麼好,怎麼實現的,日後在做我們的框架的時候偷師一手,豈不美哉。 Fresco的源碼還是比較多的,看起來會比較費勁,但是不怕,Android的系統源碼都被我們啃下來了,還怕一個小小的Fresco嗎:sunglasses:。要更好的去理解Fresco的實現,還是要從
整體入手,瞭解它的模塊和層次劃分,層層推進,逐個理解,才能達到融會貫通的效果。
由於Fresco比較大,我們先來看一下它的整體結構,有個整體的把握,Fresco的整體架構如下圖所示:
:point_right: 點擊圖片查看大圖
- DraweeView:繼承於ImageView,只是簡單的讀取xml文件的一些屬性值和做一些初始化的工作,圖層管理交由Hierarchy負責,圖層數據獲取交由負責。
- DraweeHierarchy:由多層Drawable組成,每層Drawable提供某種功能(例如:縮放、圓角)。
- DraweeController:控制數據的獲取與圖片加載,向pipeline發出請求,並接收相應事件,並根據不同事件控制Hierarchy,從DraweeView接收用戶的事件,然後執行取消網絡請求、回收資源等操作。
- DraweeHolder:統籌管理Hierarchy與DraweeHolder。
- ImagePipeline:Fresco的核心模塊,用來以各種方式(內存、磁盤、網絡等)獲取圖像。
- Producer/Consumer:Producer也有很多種,它用來完成網絡數據獲取,緩存數據獲取、圖片解碼等多種工作,它產生的結果由Consumer進行消費。
- IO/Data:這一層便是數據層了,負責實現內存緩存、磁盤緩存、網絡緩存和其他IO相關的功能。
縱觀整個Fresco的架構,DraweeView是門面,和用戶進行交互,DraweeHierarchy是視圖層級,管理圖層,DraweeController是控制器,管理數據。它們構成了整個Fresco框架的三駕馬車。當然還有我們 幕後英雄Producer,所有的髒活累活都是它乾的,最佳勞模:+1:
理解了Fresco整體的架構,我們還有了解在這套礦建裏發揮重要作用的幾個關鍵角色,如下所示:
- Supplier:提供一種特定類型的對象,Fresco裏有很多以Supplier結尾的類都實現了這個接口。
- SimpleDraweeView:這個我們就很熟悉了,它接收一個URL,然後調用Controller去加載圖片。該類繼承於GenericDraweeView,GenericDraweeView又繼承於DraweeView,DraweeView是Fresco的頂層View類。
- PipelineDraweeController:負責圖片數據的獲取與加載,它繼承於AbstractDraweeController,由PipelineDraweeControllerBuilder構建而來。AbstractDraweeController實現了DraweeController接口,DraweeController 是Fresco的數據大管家,所以的圖片數據的處理都是由它來完成的。
- GenericDraweeHierarchy:負責SimpleDraweeView上的圖層管理,由多層Drawable組成,每層Drawable提供某種功能(例如:縮放、圓角),該類由GenericDraweeHierarchyBuilder進行構建,該構建器 將placeholderImage、retryImage、failureImage、progressBarImage、background、overlays與pressedStateOverlay等 xml文件或者Java代碼裏設置的屬性信息都傳入GenericDraweeHierarchy中,由GenericDraweeHierarchy進行處理。
- DraweeHolder:該類是一個Holder類,和SimpleDraweeView關聯在一起,DraweeView是通過DraweeHolder來統一管理的。而DraweeHolder又是用來統一管理相關的Hierarchy與Controller
- DataSource:類似於Java裏的Futures,代表數據的來源,和Futures不同,它可以有多個result。
- DataSubscriber:接收DataSource返回的結果。
- ImagePipeline:用來調取獲取圖片的接口。
- Producer:加載與處理圖片,它有多種實現,例如:NetworkFetcherProducer,LocalAssetFetcherProducer,LocalFileFetchProducer。從這些類的名字我們就可以知道它們是幹什麼的。 Producer由ProducerFactory這個工廠類構建的,而且所有的Producer都是像Java的IO流那樣,可以一層嵌套一層,最終只得到一個結果,這是一個很精巧的設計:+1:
- Consumer:用來接收Producer產生的結果,它與Producer組成了生產者與消費者模式。
注:Fresco源碼裏的類的名字都比較長,但是都是按照一定的命令規律來的,例如:以Supplier結尾的類都實現了Supplier接口,它可以提供某一個類型的對象(factory, generator, builder, closure等)。 以Builder結尾的當然就是以構造者模式創建對象的類。
通過上面的描述,想必大家都Fresco有了一個整體的認識,那面對這樣龐大的一個庫,我們在去分析它的時候需要重點關注哪些點呢?��
- 圖片加載流程
- DraweeController與DraweeHierarchy
- Producer與Consumer
- 緩存機制
:point_right: 注:Fresco裏還大量運用各種設計模式,例如:Builder、Factory、Wrapper、Producer/Consumer、Adapter等,在閱讀源碼的時候,大家也要留心這些設計模式的應用與實踐。
接下來我們就帶着這4個問題去源碼中一探究竟。
一 圖片加載流程
至於分析的手段,還是老套路,先從一個簡單的例子入手,展示Fresco是如何加載圖片的,然後去分析它的圖片加載流程,讓大家有個整體的理解,然後再逐個去分析Fresco每個 子模塊的功能實現。
好,我們先來寫一個小例子。
:point_right: 舉例
初始化
Fresco.initialize(this);
加載圖片
String url = "https://github.com/guoxiaoxing/android-open-framwork-analysis/raw/master/art/fresco/scenery.jpg";
SimpleDraweeView simpleDraweeView = findViewById(R.id.drawee_view);
simpleDraweeView.setImageURI(Uri.parse(url));
我們來看一下它的調用流程,序列圖如下所示:
:point_right: 點擊圖片查看大圖
嗯,圖看起來有點大,但是不要緊,我們按照顏色將整個流程分爲了四大步:
- 初始化Fresco。
- 獲取DataSource。
- 綁定Controller與Hierarchy。
- 從內存緩存/磁盤緩存/網絡獲取圖片,並設置到對應的Drawable層。
:point_right: 注:Fresco裏的類雖多,類名雖長,但都是基於接口和Abstract類的設計,每個模塊自成一套繼承體系,所以只要掌握了它們的繼承關係以及不同模塊之間的聯繫,整個 流程還是比較簡單的。
由於序列圖設計具體細節,爲了輔助理解,我們再提供一張總結新的流程圖,如下所示:
:point_right: 點擊圖片查看大圖
接下來,我們就針對這兩張圖結合具體細節來一一分析。
1.1 初始化Fresco
:point_right: 序列圖 1.1 -> 1.11
public class Fresco {
public static void initialize(
Context context,
@Nullable ImagePipelineConfig imagePipelineConfig,
@Nullable DraweeConfig draweeConfig) {
//... 重複初始化檢驗
try {
//1. 加載so庫,這個主要是一些第三方的native庫,例如:giflib,libjpeg,libpng,
//主要用來做圖片解碼。
SoLoader.init(context, 0);
} catch (IOException e) {
throw new RuntimeException("Could not initialize SoLoader", e);
}
//2. 設置傳入的配置參數magePipelineConfig。
context = context.getApplicationContext();
if (imagePipelineConfig == null) {
ImagePipelineFactory.initialize(context);
} else {
ImagePipelineFactory.initialize(imagePipelineConfig);
}
//3. 初始化SimpleDraweeView。
initializeDrawee(context, draweeConfig);
}
private static void initializeDrawee(
Context context,
@Nullable DraweeConfig draweeConfig) {
//構建PipelineDraweeControllerBuilderSupplier對象,並傳給SimpleDraweeView。
sDraweeControllerBuilderSupplier =
new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}
}
可以發現,Fresco在初始化的過程中,主要做了三件事情:
- 加載so庫,這個主要是一些第三方的native庫,例如:giflib,libjpeg,libpng,主要用來做圖片解碼。
- 設置傳入的配置參數magePipelineConfig。
- 初始化SimpleDraweeView。
這裏面我們需要重點關注三個對象:
- ImagePipelineConfig:ImagePipeline參數配置。
- DraweeControllerBuilderSupplier:提供DraweeControllerBuilder用來構建DraweeController。
我們先來看ImagePipelineConfig,ImagePipelineConfig通過建造者模式來構建傳遞給ImagePipeline的參數,如下所示:
- Bitmap.Config mBitmapConfig; 圖片質量。
- Supplier mBitmapMemoryCacheParamsSupplier; 內存緩存的配置參數提供者。
- CountingMemoryCache.CacheTrimStrategy mBitmapMemoryCacheTrimStrategy; 內存緩存的削減策略。
- CacheKeyFactory mCacheKeyFactory; CacheKey的創建工廠。
- Context mContext; 上下文環境。
- boolean mDownsampleEnabled; 是否開啓圖片向下採樣。
- FileCacheFactory mFileCacheFactory; 磁盤緩存創建工廠。
- Supplier mEncodedMemoryCacheParamsSupplier; 未解碼圖片緩存配置參數提供者。
- ExecutorSupplier mExecutorSupplier; 線程池提供者。
- ImageCacheStatsTracker mImageCacheStatsTracker; 圖片緩存狀態追蹤器。
- ImageDecoder mImageDecoder; 圖片解碼器。
- Supplier mIsPrefetchEnabledSupplier; 是否開啓預加載。
- DiskCacheConfig mMainDiskCacheConfig; 磁盤緩存配置。
- MemoryTrimmableRegistry mMemoryTrimmableRegistry; 內存變化監聽註冊表,那些需要監聽系統內存變化的對象需要添加到這個表中類。
- NetworkFetcher mNetworkFetcher; 下載網絡圖片,默認使用內置的HttpUrlConnectionNetworkFetcher,也可以自定義。
- PlatformBitmapFactory mPlatformBitmapFactory; 根據不同的Android版本生成不同的Bitmap的工廠,主要的區別在Bitmap在內存中的位置,Android 5.0以下存儲在Ashmem中,Android 5.0以上存在Java Heap中。
- PoolFactory mPoolFactory; Bitmap池等各種池的構建工廠。
- ProgressiveJpegConfig mProgressiveJpegConfig; 漸進式JPEG配置。
- Set mRequestListeners; 請求監聽器集合,監聽請求過程中的各種事件。
- boolean mResizeAndRotateEnabledForNetwork; 是否開啓網絡圖片的壓縮和旋轉。
- DiskCacheConfig mSmallImageDiskCacheConfig; 磁盤緩存配置
- ImageDecoderConfig mImageDecoderConfig; 圖片解碼配置
- ImagePipelineExperiments mImagePipelineExperiments; Fresco提供的關於Image Pipe的實驗性功能。
上述參數基本不需要我們手動配置,除非項目上有定製性的需求。
我們可以發現,在初始化方法的最後調用initializeDrawee()給SimpleDraweeView傳入了一個PipelineDraweeControllerBuilderSupplier,這是一個很重要的對象,我們 來看看它都初始化了哪些東西。
public class PipelineDraweeControllerBuilderSupplier implements
Supplier<PipelineDraweeControllerBuilder> {
public PipelineDraweeControllerBuilderSupplier(
Context context,
ImagePipelineFactory imagePipelineFactory,
Set<ControllerListener> boundControllerListeners,
@Nullable DraweeConfig draweeConfig) {
mContext = context;
//1. 獲取ImagePipeline
mImagePipeline = imagePipelineFactory.getImagePipeline();
if (draweeConfig != null && draweeConfig.getPipelineDraweeControllerFactory() != null) {
mPipelineDraweeControllerFactory = draweeConfig.getPipelineDraweeControllerFactory();
} else {
mPipelineDraweeControllerFactory = new PipelineDraweeControllerFactory();
}
//2. 獲取PipelineDraweeControllerFactory,並初始化。
mPipelineDraweeControllerFactory.init(
context.getResources(),
DeferredReleaser.getInstance(),
imagePipelineFactory.getAnimatedDrawableFactory(context),
UiThreadImmediateExecutorService.getInstance(),
mImagePipeline.getBitmapMemoryCache(),
draweeConfig != null
? draweeConfig.getCustomDrawableFactories()
: null,
draweeConfig != null
? draweeConfig.getDebugOverlayEnabledSupplier()
: null);
mBoundControllerListeners = boundControllerListeners;
}
}
可以發現在這個方法裏初始化了兩個重要的對象:
- 獲取ImagePipeline。
- 獲取PipelineDraweeControllerFactory,並初始化。
這個PipelineDraweeControllerFactory就是用來構建PipelineDraweeController,我們前面說過PipelineDraweeController繼承於AbstractDraweeController,用來控制圖片 數據的獲取和加載,這個PipelineDraweeControllerFactory()的init()方法也是將參數裏的遍歷傳入PipelineDraweeControllerFactory中,用來準備構建PipelineDraweeController。 我們來看一下它都傳入哪些東西進去。
- context.getResources():Android的Resources對象。
- DeferredReleaser.getInstance():延遲釋放資源,等主線程處理完消息後再進行回收。
- mImagePipeline.getBitmapMemoryCache():已解碼的圖片緩存。
:point_right: 注:所謂拔出蘿蔔帶出泥,在分析圖片加載流程的時候難免會帶進來各種各樣的類,如果一時理不清它們的關係也沒關係,第一步只是要掌握整體的加載流程即可,後面 我們會對這些類逐一分析。
該方法執行完成後調用SimpleDraweeView的initizlize()方法將PipelineDraweeControllerBuilderSupplier對象設置進SimpleDraweeView的靜態對象sDraweeControllerBuilderSupplier中 整個初始化流程便完成了。
1.2 獲取DataSource
:point_right: 序列圖 2.1 -> 2.12
在分析如何生成DataSource之前,我們得先了解什麼DataSource。
DataSource是一個接口其實現類是AbstractDataSource,它可以提交數據請求,並能獲取progress、fail result與success result等信息,類似於Java裏的Future。
DataSource接口如下所示:
public interface DataSource<T> {
//數據源是否關閉
boolean isClosed();
//異步請求的結果
@Nullable T getResult();
//是否有結果返回
boolean hasResult();
//請求是否結束
boolean isFinished();
//請求是否發生錯誤
boolean hasFailed();
//發生錯誤的原因
@Nullable Throwable getFailureCause();
//請求的進度[0, 1]
float getProgress();
//結束請求,釋放資源。
boolean close();
//發送並訂閱請求,等待請求結果。
void subscribe(DataSubscriber<T> dataSubscriber, Executor executor);
}
AbstractDataSource實現了DataSource接口,它是一個基礎類,其他DataSource類都擴展自該類。AbstractDataSource實現了上述接口裏的方法,維護這DataSource的success、progress和fail的狀態。
除此之外還有以下DataSource類:
- AbstractProducerToDataSourceAdapter:繼承自AbstractDataSource,包裝了Producer取數據的過程,也就是創建了一個Consumer,詳細的過程我們後面還會說。
- CloseableProducerToDataSourceAdapter:繼承自AbstractProducerToDataSourceAdapter,實現了closeResult()方法,繪製自己銷燬時同時銷燬Result,這個是最主要使用的DataSource。
- ProducerToDataSourceAdapter:沒有實現額外的方法,僅僅用於預加載圖片。
- IncreasingQualityDataSource:內部維護一個CloseableProducerToDataSourceAdapter列表,按數據的清晰度從後往前遞增,它爲列表裏的每個DataSour測綁定一個DataSubscriber,該類負責保證 每次獲取清晰度更高的數據,獲取數據的同時銷燬清晰度更低的數據。
- FirstAvailableDataSource:內部維護一個CloseableProducerToDataSourceAdapter列表,它會返回列表裏最先獲取數據的DataSource,它爲列表裏的每個DataSour測綁定一個DataSubscriber,如果 數據加載成功,則將當前成功的DataSource指定爲目標DataSource,否則跳轉到下一個DataSource繼續嘗試。
- SettableDataSource:繼承自AbstractDataSource,並將重寫settResult()、setFailure()、setProgress()在內部調用父類的相應函數,但是修飾符變成了public(原來是protected)。即使 用SettableDataSource時可以在外部調用這三個函數設置DataSource狀態。一般用於在獲取DataSource失敗時直接產生一個設置爲Failure的DataSource。
瞭解了DataSource,我們再來看看它是如何生成的。
我們知道,在使用Fresco展示圖片的時候,只需要調用setImageURI()設置圖片URL即可,我們就以這個方法爲入口開始分析,如下所示:
public class SimpleDraweeView extends GenericDraweeView {
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller = mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build();
setController(controller);
}
}
可以發現,SimpleDraweeView將外面傳遞的URL數據封裝進了DraweeController,並調用mSimpleDraweeControllerBuilder構造了一個DraweeController對象,這個 DraweeController對象實際上就是PipelineDraweeController。
我們來看看它是如何構建的,mSimpleDraweeControllerBuilder由sDraweeControllerBuilderSupplier調用get()方法獲得,我們前面已經說過sDraweeControllerBuilderSupplier是在 SimpleDraweeView的initialize()被傳遞進來的,我們接着來看PipelineDraweeController的構建過程。
SimpleDraweeControllerBuilder是調用器父類AbstractDraweeControllerBuilder的build()方法來進行構建,而該build()方法又反過來調用其子類SimpleDraweeControllerBuilder
的obtainController()方法來完成具體子類SimpleDraweeControllerBuilder的構建,我們來看看它的實現。
:point_right: 注:Fresco的設計很好的體現了面向接口編程這一點,大部分功能都基於接口設計,然後設計出抽象類AbstractXXX,用來封裝通用的功能,個別具體的功能交由其子類實現。
public class PipelineDraweeControllerBuilder extends AbstractDraweeControllerBuilder<
PipelineDraweeControllerBuilder,
ImageRequest,
CloseableReference<CloseableImage>,
ImageInfo> {
@Override
protected PipelineDraweeController obtainController() {
DraweeController oldController = getOldController();
PipelineDraweeController controller;
//如果已經有PipelineDraweeController,則進行復用,否則構建新的PipelineDraweeController。
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;
}
}
可以發現上述函數的邏輯也很簡單,如果已經有PipelineDraweeController,則進行復用,否則調用PipelineDraweeControllerFactory.newController()方法構建 新的PipelineDraweeController。PipelineDraweeControllerFactory.newController()方法最終調用PipelineDraweeController的構造方法完成PipelineDraweeController 對象的構建,後續的流程很簡單,我們重點關注在構建的過程中傳入了哪些對象,這些對象是如何生成的。
- obtainDataSourceSupplier():獲取數據源。
- generateUniqueControllerId():生成唯一的Controller ID。
- getCacheKey():獲取緩存key。
- getCallerContext():獲取調用者的上下爲環境。
- ImmutableList 列表,用來生成各種圖片效果的Drawable。
其他的實現都比較簡單,我們重點關注obtainDataSourceSupplier()的實現,如下所示:
public class PipelineDraweeControllerBuilder extends AbstractDraweeControllerBuilder<
PipelineDraweeControllerBuilder,
ImageRequest,
CloseableReference<CloseableImage>,
ImageInfo> {
protected Supplier<DataSource<IMAGE>> obtainDataSourceSupplier() {
if (mDataSourceSupplier != null) {
return mDataSourceSupplier;
}
Supplier<DataSource<IMAGE>> supplier = null;
//1. 生成最終的image supplier。
if (mImageRequest != null) {
supplier = getDataSourceSupplierForRequest(mImageRequest);
} else if (mMultiImageRequests != null) {
supplier = getFirstAvailableDataSourceSupplier(mMultiImageRequests, mTryCacheOnlyFirst);
}
//2. 生成一個ncreasing-quality supplier,這裏會有兩級的清晰度,高清晰度的supplier優先。
if (supplier != null && mLowResImageRequest != null) {
List<Supplier<DataSource<IMAGE>>> suppliers = new ArrayList<>(2);
suppliers.add(supplier);
suppliers.add(getDataSourceSupplierForRequest(mLowResImageRequest));
supplier = IncreasingQualityDataSourceSupplier.create(suppliers);
}
//如果沒有圖片請求,則提供一個空的supplier。
if (supplier == null) {
supplier = DataSources.getFailedDataSourceSupplier(NO_REQUEST_EXCEPTION);
}
return supplier;
}
}
getDataSourceSupplierForRequest()方法最終調用(具體調用鏈可以參照序列圖,這裏就不再贅述)的是PipelineDraweeControllerBuilder的getDataSourceForRequest()
public class PipelineDraweeControllerBuilder extends AbstractDraweeControllerBuilder<
PipelineDraweeControllerBuilder,
ImageRequest,
CloseableReference<CloseableImage>,
ImageInfo> {
@Override
protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(
ImageRequest imageRequest,
Object callerContext,
AbstractDraweeControllerBuilder.CacheLevel cacheLevel) {
//調用ImagePipeline的fetchDecodedImage()方法獲取DataSource
return mImagePipeline.fetchDecodedImage(
imageRequest,
callerContext,
convertCacheLevelToRequestLevel(cacheLevel));
}
}
ImagePipeline是Fresco Image Pipeline的入口類,前面也說過ImagePipeline是Fresco的核心模塊,用來以各種方式(內存、磁盤、網絡等)獲取圖像。
這個mImagePipeline就是在PipelineDraweeControllerBuilderSupplier中調用ImagePipelineFactory的getImagePipeline()方法創建的。 我們接着來看ImagePipeline的fetchDecodedImage()方法,如下所示:
public class ImagePipeline {
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
ImageRequest imageRequest,
Object callerContext,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit) {
try {
//1. 獲取Producer序列,爲DataSource提供不同的數據輸入管道。
Producer<CloseableReference<CloseableImage>> producerSequence =
mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
//2. 調用submitFetchRequest()方法生成DataSource。
return submitFetchRequest(
producerSequence,
imageRequest,
lowestPermittedRequestLevelOnSubmit,
callerContext);
} catch (Exception exception) {
return DataSources.immediateFailedDataSource(exception);
}
}
}
關於什麼是Producer,我們前面也已經說過。
Producer用來加載與處理圖片,它有多種實現,例如:NetworkFetcherProducer,LocalAssetFetcherProducer,LocalFileFetchProducer。從這些類的名字我們就可以知道它們是幹什麼的。 Producer由ProducerFactory這個工廠類構建的,而且所有的Producer都是像Java的IO流那樣,可以一層嵌套一層,最終只得到一個結果,
關於Producer的更多內容,我們後面會專門講,這個方法主要做了兩件事情:
- 獲取Producer序列,爲DataSource提供不同的數據輸入管道,Producer是由很多種的,代表從不同途徑獲取圖片數據,我們下面會詳細講。
- 調用submitFetchRequest()方法生成DataSource。
可以發現該方法最終調用submitFetchRequest()方法生成了DataSource,如下所示:
public class ImagePipeline {
private <T> DataSource<CloseableReference<T>> submitFetchRequest(
Producer<CloseableReference<T>> producerSequence,
ImageRequest imageRequest,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit,
Object callerContext) {
final RequestListener requestListener = getRequestListenerForRequest(imageRequest);
try {
//1. 獲取緩存級別,RequestLevel將緩存分爲四級 FULL_FETCH(1) 從網絡或者本地存儲獲取,DISK_CACHE(2) 從磁盤緩存獲取,ENCODED_MEMORY_CACHE(3)從
//未接嗎的內存緩存獲取,BITMAP_MEMORY_CACHE(4)已解碼的內存緩存獲取。
ImageRequest.RequestLevel lowestPermittedRequestLevel =
ImageRequest.RequestLevel.getMax(
imageRequest.getLowestPermittedRequestLevel(),
lowestPermittedRequestLevelOnSubmit);
//2. 將ImageRequest、RequestListener等信息封裝進SettableProducerContext,ProducerContext是Producer
//的上下文環境,利用ProducerContext可以改變Producer內部的狀態。
SettableProducerContext settableProducerContext = new SettableProducerContext(
imageRequest,
generateUniqueFutureId(),
requestListener,
callerContext,
lowestPermittedRequestLevel,
/* isPrefetch */ false,
imageRequest.getProgressiveRenderingEnabled() ||
imageRequest.getMediaVariations() != null ||
!UriUtil.isNetworkUri(imageRequest.getSourceUri()),
imageRequest.getPriority());
//3. 創建CloseableProducerToDataSourceAdapter,CloseableProducerToDataSourceAdapter是DataSource的一種。
return CloseableProducerToDataSourceAdapter.create(
producerSequence,
settableProducerContext,
requestListener);
} catch (Exception exception) {
return DataSources.immediateFailedDataSource(exception);
}
}
}
該方法主要做了三件事情:
- 獲取緩存級別,RequestLevel將緩存分爲四級 FULL FETCH(1) 從網絡或者本地存儲獲取,DISK CACHE(2) 從磁盤緩存獲取,ENCODED MEMORY CACHE(3)從
未接嗎的內存緩存獲取,BITMAP MEMORY CACHE(4)已解碼的內存緩存獲取。 - 將ImageRequest、RequestListener等信息封裝進SettableProducerContext,ProducerContext是Producer
的上下文環境,利用ProducerContext可以改變Producer內部的狀態。 - 創建CloseableProducerToDataSourceAdapter,CloseableProducerToDataSourceAdapter是DataSource的一種。
接着CloseableProducerToDataSourceAdapter調用了自己create()方法構建一個CloseableProducerToDataSourceAdapter對象。至此DataSource已經完成完成了,然後把它設置到 PipelineDraweeController裏。
我們接着來看綁定Controller與Hierarchy的流程。:point_down:
1.3 綁定DraweeController與DraweeHierarchy
:point_right: 序列圖 3.1 -> 3.7
前面提到在SimpleDraweeView的setImageURI()方法裏會爲SimpleDraweeView設置前面構建好的PipelineDraweeController,如下所示:
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller = mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build();
setController(controller);
}
從上面的序列圖得知,setController()方法經過層層調用,最終調用的是DraweeHolder的setController()方法,DraweeHolder用來統籌管理Controller與Hierarchy,它是DraweeView的一個 成員變量,在DraweeHolder對象初始化的時候被構建,我們來看看它的setController()方法,如下所示:
public class DraweeHolder<DH extends DraweeHierarchy>
implements VisibilityCallback {
public void setController(@Nullable DraweeController draweeController) {
boolean wasAttached = mIsControllerAttached;
//1. 如果已經和Controller建立聯繫,則先detach。
if (wasAttached) {
detachController();
}
//2. 清楚舊的Controller。
if (isControllerValid()) {
mEventTracker.recordEvent(Event.ON_CLEAR_OLD_CONTROLLER);
mController.setHierarchy(null);
}
//3. 爲Controller重新設置Hierarchy)建立新的Controller。
mController = draweeController;
if (mController != null) {
mEventTracker.recordEvent(Event.ON_SET_CONTROLLER);
mController.setHierarchy(mHierarchy);
} else {
mEventTracker.recordEvent(Event.ON_CLEAR_CONTROLLER);
}
//4. 對DraweeHolder和Controller進行attach操作。
if (wasAttached) {
attachController();
}
}
}
上述方法的流程也十分簡單,如下所示:
- 如果已經和Controller建立聯繫,則先detach。
- 清楚舊的Controller。
- 爲Controller重新設置Hierarchy)建立新的Controller。
- 對DraweeHolder和Controller進行attach操作。
上述流程裏有兩個關鍵的地方:設置Hierarchy和attch操作,我們分別來看看,
從上面的序列圖可以看出,這個mHierarchy是在GenricDraweeView的構造方法裏調用inflateHierarchy()方法創建的,它實際上是一個GenericDraweeHierarchy對象,而setHierarchy()方法 最終調用的是AbstractDraweeController的setHierarchy()方法,如下所示:
public abstract class AbstractDraweeController<T, INFO> implements
DraweeController,
DeferredReleaser.Releasable,
GestureDetector.ClickListener {
public void setHierarchy(@Nullable DraweeHierarchy hierarchy) {
//... log
mEventTracker.recordEvent(
(hierarchy != null) ? Event.ON_SET_HIERARCHY : Event.ON_CLEAR_HIERARCHY);
//1. 釋放掉當前正在進行的請求。
if (mIsRequestSubmitted) {
mDeferredReleaser.cancelDeferredRelease(this);
release();
}
//2. 清除已經存在的Hierarchy。
if (mSettableDraweeHierarchy != null) {
mSettableDraweeHierarchy.setControllerOverlay(null);
mSettableDraweeHierarchy = null;
}
//3. 設置新的Hierarchy。
if (hierarchy != null) {
Preconditions.checkArgument(hierarchy instanceof SettableDraweeHierarchy);
mSettableDraweeHierarchy = (SettableDraweeHierarchy) hierarchy;
mSettableDraweeHierarchy.setControllerOverlay(mControllerOverlay);
}
}
}
這個mSettableDraweeHierarchy實際的實現類是GenericDraweeHierarchy,
走到這裏,DraweeController與DraweeHierarchy的綁定流程就完成了。
1.4 從內存緩存/磁盤緩存/網絡獲取圖片,並設置到對應的Drawable層
:point_right: 序列圖 4.1 -> 4.14
這一塊的內容主要執行上面創建的各種Producer,從從內存緩存/磁盤緩存/網絡獲取圖片,並調用對應的Consumer消費結果,最終 不同的Drawable設置到對應的圖層中去,關於DraweeHierarchy與Producer我們下面都會詳細的講,我們先來看看上面層層請求到 圖片最終是如何設置到SimpleDraweeView中去的,如下所示:
public class GenericDraweeHierarchy implements SettableDraweeHierarchy {
@Override
public void setImage(Drawable drawable, float progress, boolean immediate) {
drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources);
drawable.mutate();
//mActualImageWrapper就是實際加載圖片的那個圖層,此處要設置的SimpleDraweeView最終要顯示的圖片。
mActualImageWrapper.setDrawable(drawable);
mFadeDrawable.beginBatchMode();
fadeOutBranches();
fadeInLayer(ACTUAL_IMAGE_INDEX);
setProgress(progress);
if (immediate) {
mFadeDrawable.finishTransitionImmediately();
}
mFadeDrawable.endBatchMode();
}
}
mActualImageWrapper就是實際加載圖片的那個圖層,此處要設置的SimpleDraweeView最終要顯示的圖片。
如此,一個SimpleDraweeView的圖片加載流程就完成了,面對如此長的流程,讀者不免疑惑,我們只要掌握了整體流程,就可以 分而治之,逐個擊破。
二 DraweeHierarchy
Fresco的圖片效果是依賴於Drawee實現的,也就是Drawable層級。
DraweeHierarchy是Fresco裏的Drawable層級,它是一層一層疊加在DraweeView上的來實現各種效果,例如:佔位圖、失敗圖、加載進度圖等,DraweeHierarchy是一個接口,它還有個 子接口SettableDraweeHierarchy,它們的實現類是GenericDraweeHierarchy。
DraweeHierarchy接口與SettableDraweeHierarchy接口如下所示:
public interface DraweeHierarchy {
//獲取頂層的Drawable,也就是其父節點的圖層
Drawable getTopLevelDrawable();
}
public interface SettableDraweeHierarchy extends DraweeHierarchy {
//由DraweeController調用,重置DraweeHierarchy狀態
void reset();
//由DraweeController調用,設置圖片數據,progress在漸進式JPEG裏使用,immediate表示是否立即顯示這張圖片
void setImage(Drawable drawable, float progress, boolean immediate);
//由DraweeController調用,更新圖片加載進度【0, 1】,progress爲1或者immediate爲true時的時候會隱藏進度條。
void setProgress(float progress, boolean immediate);
//由DraweeController調用,設置失敗原因,DraweeHierarchy可以根據不同的原因展示不同的失敗圖片。
void setFailure(Throwable throwable);
//由DraweeController調用,設置重試原因,DraweeHierarchy可以根據不同的原因展示不同的重試圖片。
void setRetry(Throwable throwable);
//由DraweeController調用,設置其他的Controller覆蓋層
void setControllerOverlay(Drawable drawable);
}
理解了DraweeHierarchy的大致接口,我們繼續從以下幾個角度來解析DraweeHierarchy:
- 圖層的層級構造
- 圖層的創建流程
2.1 圖層的層級構造
Fresco裏定義了許多Drawable,它們都直接或者間接的繼承了Drawable,來實現不同的功能。它們的圖層層級如下所示:
o RootDrawable (top level drawable)
|
+--o FadeDrawable
|
+--o ScaleTypeDrawable (placeholder branch, optional)
| |
| +--o Drawable (placeholder image)
|
+--o ScaleTypeDrawable (actual image branch)
| |
| +--o ForwardingDrawable (actual image wrapper)
| |
| +--o Drawable (actual image)
|
+--o null (progress bar branch, optional)
|
+--o Drawable (retry image branch, optional)
|
+--o ScaleTypeDrawable (failure image branch, optional)
|
+--o Drawable (failure image)
`
Fresco裏的Drawable子類有很多,按照功能劃分可以分爲三大類:
容器類Drawable
- ArrayDrawable:內部存儲着一個Drawable數組,與Android裏的LayerDrawable類似,將數組裏的Drawable當作圖層,按照數組的順序繪製Drawable,數組最後 的成員會在最上方,不過它和LayerDrawable也有不同的地方:① 繪製順序是數組的順序,但是ArrayDrawable會跳過暫時不需要繪製的圖層。② 不支持動態添加圖層。
- FadeDrawable:繼承與ArrayDrawable,除了具有ArrayDrawable的功能外,它還可以隱藏和顯示圖層。
容器類Drawable
- ForwardingDrawable:內部爲患者一個Drawable成員變量,將Drawable的一些基本操作和回調傳遞給目標Drawable,它是所以容器類Drawable的基類。
- ScaleTypeDrawable:繼承於ForwardingDrawable,封裝了對代理圖片的縮放處理。
- SettableDrawable:繼承於ForwardingDrawable,可以多次設置內容Drawable的容器,多用在目標圖片的圖層中。
- AutoRotateDrawable:繼承於ForwardingDrawable,提供內容動態旋轉的容器。
- OrientedDrawable:繼承於ForwardingDrawable,可以將內容Drawable以一個特定的角度繪製的容器。
- MatrixDrawable:繼承於ForwardingDrawable,可以爲內容應用變形矩陣的容器,它只能賦予給顯示目標圖片的那個圖層。不能在一個圖層上同時使用MatrixDrawable與ScaleTypeDrawable!
- RoundedCornersDrawable:繼承於ForwardingDrawable,可以將內容的邊界修剪成圓角矩形(目前版本暫不支持)或用實心的圓角矩形覆蓋內容的容器。
- GenericDraweeHierarchy.RootDrawable:繼承於ForwardingDrawable,專門用於頂層圖層的容器。
視圖類Drawable
- ProgressBarDrawable:負責繪製進度條。
- RoundedBitmapDrawable:將自身內容修剪成圓角矩形繪製出來,可以使用Bitmap作爲對象,返回一個BitmapDrawable。
除了這些Drawable類以爲,還有一個Drawable接口,凡是做matrix變換和圓角處理的Drawable都實現了這個接口,這是爲了子Drawable可以父Drawable的 變換矩陣和圓角節點,以便可以正確的繪製自己。
如下所示:
public interface TransformAwareDrawable {
//設置TransformCallback回調
void setTransformCallback(TransformCallback transformCallback);
}
public interface TransformCallback {
//獲取應用在Drawable上的所有matrices矩陣,存儲在transform中
void getTransform(Matrix transform);
//獲取Drawable的根節點邊界,存儲在bounds中。
void getRootBounds(RectF bounds);
}
從用戶的角度,SimpleDraweeView上的圖層主要被分成了以下幾層:
- 背景圖(backgroundImage)
- 佔位圖(placeholderImage=)
- 加載的圖片(actualImage)
- 進度條(progressBarImage)
- 重試加載的圖片(retryImage)
- 失敗圖片(failureImage)
- 疊加圖(overlayImage)
理解了圖層的層級構造,我們接着來看看圖層的創建流程。:point_down:
2.2 圖層的創建流程
我們前面說過在GenericDraweeView的構造方法裏,調用了它的inflateHierarchy()方法構建了一個GenericDraweeHierarchy對象,GenericDraweeHierarchy的實際 是由GenericDraweeHierarchyBuild調用build()方法來完成的。
GenericDraweeHierarchy是負責加載每個圖層信息的載體,我們來看看它的構造方法的實現,如下所示:
public class GenericDraweeHierarchy implements SettableDraweeHierarchy {
//就跟我們上面說的一樣,7個圖層。
//背景圖層
private static final int BACKGROUND_IMAGE_INDEX = 0;
//佔位圖層
private static final int PLACEHOLDER_IMAGE_INDEX = 1;
//加載的圖片圖層
private static final int ACTUAL_IMAGE_INDEX = 2;
//進度條
private static final int PROGRESS_BAR_IMAGE_INDEX = 3;
//重試加載的圖片
private static final int RETRY_IMAGE_INDEX = 4;
//失敗圖片
private static final int FAILURE_IMAGE_INDEX = 5;
//疊加圖
private static final int OVERLAY_IMAGES_INDEX = 6;
GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
mResources = builder.getResources();
mRoundingParams = builder.getRoundingParams();
//實際加載圖片的Drawable
mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable);
int numOverlays = (builder.getOverlays() != null) ? builder.getOverlays().size() : 1;
numOverlays += (builder.getPressedStateOverlay() != null) ? 1 : 0;
//圖層數量
int numLayers = OVERLAY_IMAGES_INDEX + numOverlays;
// array of layers
Drawable[] layers = new Drawable[numLayers];
//1. 構建背景圖層Drawable。
layers[BACKGROUND_IMAGE_INDEX] = buildBranch(builder.getBackground(), null);
//2. 構建佔位圖層Drawable。
layers[PLACEHOLDER_IMAGE_INDEX] = buildBranch(
builder.getPlaceholderImage(),
builder.getPlaceholderImageScaleType());
//3. 構建加載的圖片圖層Drawable。
layers[ACTUAL_IMAGE_INDEX] = buildActualImageBranch(
mActualImageWrapper,
builder.getActualImageScaleType(),
builder.getActualImageFocusPoint(),
builder.getActualImageColorFilter());
//4. 構建進度條圖層Drawable。
layers[PROGRESS_BAR_IMAGE_INDEX] = buildBranch(
builder.getProgressBarImage(),
builder.getProgressBarImageScaleType());
//5. 構建重新加載的圖片圖層Drawable。
layers[RETRY_IMAGE_INDEX] = buildBranch(
builder.getRetryImage(),
builder.getRetryImageScaleType());
//6. 構建失敗圖片圖層Drawable。
layers[FAILURE_IMAGE_INDEX] = buildBranch(
builder.getFailureImage(),
builder.getFailureImageScaleType());
if (numOverlays > 0) {
int index = 0;
if (builder.getOverlays() != null) {
for (Drawable overlay : builder.getOverlays()) {
//7. 構建疊加圖圖層Drawable。
layers[OVERLAY_IMAGES_INDEX + index++] = buildBranch(overlay, null);
}
} else {
index = 1; // reserve space for one overlay
}
if (builder.getPressedStateOverlay() != null) {
layers[OVERLAY_IMAGES_INDEX + index] = buildBranch(builder.getPressedStateOverlay(), null);
}
}
// fade drawable composed of layers
mFadeDrawable = new FadeDrawable(layers);
mFadeDrawable.setTransitionDuration(builder.getFadeDuration());
// rounded corners drawable (optional)
Drawable maybeRoundedDrawable =
WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams);
// top-level drawable
mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable);
mTopLevelDrawable.mutate();
resetFade();
}
}
這個方法主要是構建各個圖層的Drawable對象,如下所示:
- 構建背景圖層Drawable。
- 構建佔位圖層Drawable。
- 構建加載的圖片圖層Drawable。
- 構建進度條圖層Drawable。
- 構建重新加載的圖片圖層Drawable。
- 構建失敗圖片圖層Drawable。
- 構建疊加圖圖層Drawable。
而構建的方法設計到兩個方法
public class GenericDraweeHierarchy implements SettableDraweeHierarchy {
@Nullable
private Drawable buildActualImageBranch(
Drawable drawable,
@Nullable ScaleType scaleType,
@Nullable PointF focusPoint,
@Nullable ColorFilter colorFilter) {
drawable.setColorFilter(colorFilter);
drawable = WrappingUtils.maybeWrapWithScaleType(drawable, scaleType, focusPoint);
return drawable;
}
/** Applies scale type and rounding (both if specified). */
@Nullable
private Drawable buildBranch(@Nullable Drawable drawable, @Nullable ScaleType scaleType) {
//如果需要爲Drawable設置Round,RoundedBitmapDrawable或者RoundedColorDrawable。
drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources);
//如果需要爲Drawable設置ScaleType,則將它包裝成一個ScaleTypeDrawable。
drawable = WrappingUtils.maybeWrapWithScaleType(drawable, scaleType);
return drawable;
}
}
構建Drawable的過程中都要應用相應的縮放類型和圓角角度,如下所示:
- 如果需要爲Drawable設置Round,RoundedBitmapDrawable或者RoundedColorDrawable。
- 如果需要爲Drawable設置ScaleType,則將它包裝成一個ScaleTypeDrawable。
這樣一個圖層的載體GenericDraweeHierarchy就構建完成了,後續GenericDraweeHierarchy裏的各種操作都是調用器內部的各種Drawable的方法來完成的。
三 Producer與Consumer
我們前面說過Producer是Fresco的最佳勞模,所有的髒話累活都是它乾的,我們來看看它的實現。
public interface Producer<T> {
//開始處理任務,執行的結果右Consumer進行消費。
void produceResults(Consumer<T> consumer, ProducerContext context);
}
Fresco裏實現了多個Producer,按照功能劃分可以分爲以下幾類:
本地數據獲取P類roducer,這類Producer負責從本地獲取數據。
- LocalFetchProducer:實現了Producer接口,所有本地數據Producer獲取的基類。
- LocalAssetFetchProducer 繼承於LocalFetchProducer,通過AssetManager獲取ImageRequest對象的輸入流及對象字節碼長度,將它轉換爲EncodedImage;
- LocalContentUriFetchProducer 繼承於LocalFetchProducer,若Uri指向聯繫人,則獲取聯繫人頭像;若指向相冊圖片,則會根據是否傳入ResizeOption進行一定縮放(這裏不是完全按ResizeOption縮放);若止這兩個條件都不滿足,則直接調用ContentResolver的函數openInputStream(Uri uri)獲取輸入流並轉化爲EncodedImage;
- LocalFileFetchProducer 繼承於LocalFetchProducer,直接通過指定文件獲取輸入流,從而轉化爲EncodedImage;
-
LocalResourceFetchProducer 繼承於LocalFetchProducer,通過Resources的函數openRawResources獲取輸入流,從而轉化爲EncodedImage。
-
LocalExifThumbnailProducer 沒有繼承於LocalFetchProducer,可以獲取Exif圖像的Producer;
- LocalVideoThumbnailProducer 沒有繼承於LocalFetchProducer,可以獲取視頻縮略圖的Producer。
網絡數據獲取類Producer,這類Producer負責從網絡獲取數據。
- NetworkFetchProducer:實現了Producer接口,從網絡上獲取圖片數據。
緩存數據獲取類Producer,這類Producer負責從緩存中獲取數據。
- BitmapMemoryCacheGetProducer 它是一個Immutable的Producer,僅用於包裝後續Producer;
- BitmapMemoryCacheProducer 在已解碼的內存緩存中獲取數據;若未找到,則在nextProducer中獲取數據,並在獲取到數據的同時將其緩存;
- BitmapMemoryCacheKeyMultiplexProducer 是MultiplexProducer的子類,nextProducer爲BitmapMemoryCacheProducer,將多個擁有相同已解碼內存緩存鍵的ImageRequest進行“合併”,若緩存命中,它們都會獲取到該數據;
- PostprocessedBitmapMemoryCacheProducer 在已解碼的內存緩存中尋找PostProcessor處理過的圖片。它的nextProducer都是PostProcessorProducer,因爲如果沒有獲取到被PostProcess的緩存,就需要對獲取的圖片進行PostProcess。;若未找到,則在nextProducer中獲取數據;
- EncodedMemoryCacheProducer 在未解碼的內存緩存中尋找數據,如果找到則返回,使用結束後釋放資源;若未找到,則在nextProducer中獲取數據,並在獲取到數據的同時將其緩存;
- EncodedCacheKeyMultiplexProducer 是MultiplexProducer的子類,nextProducer爲EncodedMemoryCacheProducer,將多個擁有相同未解碼內存緩存鍵的ImageRequest進行“合併”,若緩存命中,它們都會獲取到該數據;
- DiskCacheProducer 在文件內存緩存中獲取數據;若未找到,則在nextProducer中獲取數據,並在獲取到數據的同時將其緩存
功能類Producer,這類Producer在初始化的時候會傳入一個nextProducer,它們會對nextProducer產生的結果進行處理。
- MultiplexProducer 將多個擁有相同CacheKey的ImageRequest進行“合併”,讓他們從都從nextProducer中獲取數據;
- ThreadHandoffProducer 將nextProducer的produceResult方法放在後臺線程中執行(線程池容量爲1);
- SwallowResultProducer 將nextProducer的獲取的數據“吞”掉,回在Consumer的onNewResult中傳入null值;
- ResizeAndRotateProducer 將nextProducer產生的EncodedImage根據EXIF的旋轉、縮放屬性進行變換(如果對象不是JPEG格式圖像,則不會發生變換);
- PostProcessorProducer 將nextProducer產生的EncodedImage根據PostProcessor進行修改,關於PostProcessor詳見修改圖片;
- DecodeProducer 將nextProducer產生的EncodedImage解碼。解碼在後臺線程中執行,可以在ImagePipelineConfig中通過setExecutorSupplier來設置線程池數量,默認爲最大可用的處理器數;
- WebpTranscodeProducer 若nextProducer產生的EncodedImage爲WebP格式,則將其解碼成DecodeProducer能夠處理的EncodedImage。解碼在後代進程中進行。
那麼這些Producer是在哪裏構建的呢?��
我們前面說過,在構建DataSource的時候,會調用ProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);方法爲指定的ImageRequest構建 想要的Producer序列,事實上,ProducerSequenceFactory裏除了getDecodedImageProducerSequence()方法以爲,還有幾個針對其他情況獲取序列的方法,這裏我們 列一下從網絡獲取圖片的時候Producer序列是什麼樣的。
如下所示:
- PostprocessedBitmapMemoryCacheProducer,非必須 ,在Bitmap緩存中查找被PostProcess過的數據。
- PostprocessorProducer,非必須,對下層Producer傳上來的數據進行PostProcess。
- BitmapMemoryCacheGetProducer,必須,使Producer序列只讀。
- ThreadHandoffProducer,必須,使下層Producer工作在後臺進程中執行。
- BitmapMemoryCacheKeyMultiplexProducer,必須,使多個相同已解碼內存緩存鍵的ImageRequest都從相同Producer中獲取數據。
- BitmapMemoryCacheProducer,必須,從已解碼的內存緩存中獲取數據。
- DecodeProducer,必須,將下層Producer產生的數據解碼。
- ResizeAndRotateProducer,非必須,將下層Producer產生的數據變換。
- EncodedCacheKeyMultiplexProducer,必須,使多個相同未解碼內存緩存鍵的ImageRequest都從相同Producer中獲取數據。
- EncodedMemoryCacheProducer,必須,從未解碼的內存緩存中獲取數據。
- DiskCacheProducer,必須,從文件緩存中獲取數據。
- WebpTranscodeProducer,非必須,將下層Producer產生的Webp(如果是的話)進行解碼。
- NetworkFetchProducer,必須,從網絡上獲取數據。
我們上面說道Producer產生的結果由Consumer來消費,那它又是如何創建的呢?��
Producer在處理數據時是向下傳遞的,而Consumer來接收結果時則是向上傳遞的,基本上Producer在接收上層傳遞的Consumer進行包裝,我們舉個小例子。
在上面的流程分析中,我們說過最終創建的DataSource是CloseableProducerToDataSourceAdapter,CloseableProducerToDataSourceAdapter的父類是AbstractProducerToDataSourceAdapter,在它的 構造方法中會調用createConsumer()來創建第一層Consumer,如下所示:
public abstract class AbstractProducerToDataSourceAdapter<T> extends AbstractDataSource<T> {
private Consumer<T> createConsumer() {
return new BaseConsumer<T>() {
@Override
protected void onNewResultImpl(@Nullable T newResult, @Status int status) {
AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, status);
}
@Override
protected void onFailureImpl(Throwable throwable) {
AbstractProducerToDataSourceAdapter.this.onFailureImpl(throwable);
}
@Override
protected void onCancellationImpl() {
AbstractProducerToDataSourceAdapter.this.onCancellationImpl();
}
@Override
protected void onProgressUpdateImpl(float progress) {
AbstractProducerToDataSourceAdapter.this.setProgress(progress);
}
};
}
}
從上面列出的Producer序列可以看出,第一層Producer就是PostprocessedBitmapMemoryCacheProducer,在它的produceResults()方法中,會對上面傳遞下來的Consumer進行包裝,如下所示:
public class PostprocessedBitmapMemoryCacheProducer
implements Producer<CloseableReference<CloseableImage>> {
@Override
public void produceResults(
final Consumer<CloseableReference<CloseableImage>> consumer,
final ProducerContext producerContext) {
//...
final boolean isRepeatedProcessor = postprocessor instanceof RepeatedPostprocessor;
Consumer<CloseableReference<CloseableImage>> cachedConsumer = new CachedPostprocessorConsumer(
consumer,
cacheKey,
isRepeatedProcessor,
mMemoryCache);
listener.onProducerFinishWithSuccess(
requestId,
getProducerName(),
listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null);
mInputProducer.produceResults(cachedConsumer, producerContext);
//...
}
}
當PostprocessedBitmapMemoryCacheProducer調用自己的produceResults()處理自己的任務時,會繼續調用下一層的Producer,當所有的Producer都完成自己的工作 以後,結果就由下至上層層返回到最上層的Consumer回調中,最終將結果返回給調用者。
Fresco裏的Producer是按照一定的順序進行排列,一個執行完了,繼續執行下一個。
以上便是Fresco裏整個Producer/Consumer結構。
三 緩存機制
Fresco裏有三級緩存,兩級內存緩存,一級磁盤緩存,如下圖所示:
:point_right: 點擊圖片查看大圖
- 未編碼圖片內存緩存
- 已編碼圖片內存緩存
- 磁盤緩存
磁盤緩存因爲涉及到文件讀寫要比內存緩存複雜一些,從下至上可以將磁盤緩存分爲三層:
- 緩衝緩存層:由BufferedDiskCache實現,提供緩衝功能。
- 文件緩存層:由DiskStroageCache實現,提供實際的緩存功能。
- 文件存儲層:由DefaultDiskStorage實現,提供磁盤文件讀寫的功能。
我們先來看看Fresco的緩存鍵值的設計,Fresco爲緩存鍵設計了一個接口,如下所示:
public interface CacheKey {
String toString();
boolean equals(Object o);
int hashCode();
//是否是由Uri構建而來的
boolean containsUri(Uri uri);
//獲取url string
String getUriString();
}
CacheKey有兩個實現類:
- BitmapMemoryCacheKey 用於已解碼的內存緩存鍵,會對Uri字符串、縮放尺寸、解碼參數、PostProcessor等關鍵參數進行hashCode作爲唯一標識;
- SimpleCacheKey 普通的緩存鍵實現,使用傳入字符串的hashCode作爲唯一標識,所以需要保證相同鍵傳入字符串相同。
好,我們繼續來分析內存緩存和磁盤緩存的實現。
3.1 內存緩存
我們前面說到,內存緩存分爲兩級:
- 未解碼圖片內存緩存:由EncodedImage描述真正的緩存對象。
- 已解碼圖片內存緩存:由BitmapMemoryCache描述真正的緩存對象。
它們的區別在於緩存的數據格式不同,未編碼圖片內存緩存使用的是CloseableReference ,已編碼圖片內存緩存使用的是CloseableReference ,它們的區別在於資源 的測量和釋放方式是不同,它們使用VauleDescriptor來描述不同資源的數據大小,使用不同的ResourceReleaser來釋放資源。
內部的數據結構使用的是CountingLruMap,我們之前在文章 07Android開源框架源碼賞析:LruCache與DiskLruCache 中 提到,LruCache與DiskLruCache都使用的是LinkedHashMap,Fresco沒有直接使用LinkedHashMap,而是對它做了一層封裝,這個就是CountingLruMap,它內部有一個雙向鏈表,在查找的時候,可以從最 早插入的單位開始查詢,這樣就可以快速刪除掉最早插入的數據,提高效率。
我們接着來看內存緩存是如何實現的,內存緩存的實現源於一個共同的接口,如下所示:
public interface MemoryCache<K, V> {
//緩存指定的key-value對,該方法會返回一份新的緩存拷貝用來代碼原有的引用,但不需要的時候需要關閉這個緩存引用
CloseableReference<V> cache(K key, CloseableReference<V> value);
//獲取緩存
CloseableReference<V> get(K key);
//根據指定的key移除緩存
public int removeAll(Predicate<K> predicate);
//查詢是否包含該key對應的緩存
public boolean contains(Predicate<K> predicate);
}
和內存緩存相關的還有一個接口MemoryTrimmable,實現該接口,並將自己註冊的MemoryTrimmableRegistry中,當內存變化時,可以 通知到自己,如下所示:
public interface MemoryTrimmable {
//內存發生變化
void trim(MemoryTrimType trimType);
}
我們來看看有哪些類直接或者間接實現了該緩存接口。
- CountingMemoryCache。它實現了MemoryCache與MemoryTrimmable接口,內部維護這一個Entry用來封裝緩存對象,Entry對象除了記錄緩存鍵、緩存值之外,還記錄着 該對象的引用數量(clientCount),以及是否被緩存追蹤(isOrphan)。
- InstrumentedMemoryCache:也實現了MemoryCache接口,但它沒有直接實現相應的功能,它相當於是個Wrapper類,對CountingMemoryCache進行了包裝。增加了MemoryCacheTracker ,在緩存未命中時提供回調函數,供調用者實現自定義功能。
在CountingMemoryCache內部使用Entry對象來描述緩存對,它包含以下信息:
static class Entry<K, V> {
//緩存key
public final K key;
//緩存對象
public final CloseableReference<V> valueRef;
// The number of clients that reference the value.
//緩存的引用計數
public int clientCount;
//該Entry對象是否被其所描述的緩存所追蹤
public boolean isOrphan;
//緩存狀態監聽器
@Nullable public final EntryStateObserver<K> observer;
}
:point_right: 注:只有引用數量(clientCount)爲0,且沒有被緩存追蹤(isOrphan = true)時緩存對象纔可以被釋放。
我們接着開看看CountingMemoryCache是如何插入、獲取和刪除緩存的。
插入緩存
首先我們要了解緩存的操作涉及到兩個集合:
//待移除緩存集合,這裏面的緩存沒有被外面使用
@VisibleForTesting
final CountingLruMap<K, Entry<K, V>> mExclusiveEntries;
//所有緩存的集合,包括待移除的緩存
@GuardedBy("this")
@VisibleForTesting
final CountingLruMap<K, Entry<K, V>> mCachedEntries;
我們接着來看插入緩存的實現。
public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, MemoryTrimmable {
public CloseableReference<V> cache(
final K key,
final CloseableReference<V> valueRef,
final EntryStateObserver<K> observer) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(valueRef);
//1. 檢查是否需要更新緩存參數。
maybeUpdateCacheParams();
Entry<K, V> oldExclusive;
CloseableReference<V> oldRefToClose = null;
CloseableReference<V> clientRef = null;
synchronized (this) {
//2. 在緩存中查找要插入的對象,若存在則將其從待移除緩存集合移除,並調用它的close()方法
//當該緩存對象的引用數目爲0的時候會釋放掉該對象。
oldExclusive = mExclusiveEntries.remove(key);
Entry<K, V> oldEntry = mCachedEntries.remove(key);
if (oldEntry != null) {
makeOrphan(oldEntry);
oldRefToClose = referenceToClose(oldEntry);
}
//3. 檢查是否緩存對象達到最大顯示或者緩存池已滿,如果都爲否,則插入新緩存對象。
if (canCacheNewValue(valueRef.get())) {
Entry<K, V> newEntry = Entry.of(key, valueRef, observer);
mCachedEntries.put(key, newEntry);
//4. 將插入的對象包裝成一個CloseableReference,重新包裝對象主要是爲了重設
//一下ResourceReleaser,它會在釋放資源的時候減少Entry的clientCount,並將該緩存對象
// 加入到mExclusiveEntries中,mExclusiveEntries裏存放的是已經被使用過的緩存(等待被釋放),
// 如果緩存對象可以釋放,則直接釋放緩存對象。
clientRef = newClientReference(newEntry);
}
}
CloseableReference.closeSafely(oldRefToClose);
maybeNotifyExclusiveEntryRemoval(oldExclusive);
//5. 判斷是否需要釋放資源,當超過了EvictEntries最大容量或者緩存池已滿,則移除EvictEntries最早插入的對象。
maybeEvictEntries();
return clientRef;
}
}
插入緩存主要做了以下幾件事情:
- 檢查是否需要更新緩存參數。
- 在緩存中查找要插入的對象,若存在則將其從待移除緩存集合移除,並調用它的close()方法當該緩存對象的引用數目爲0的時候會釋放掉該對象。
- 檢查是否緩存對象達到最大顯示或者緩存池已滿,如果都爲否,則插入新緩存對象。
- 將插入的對象包裝成一個CloseableReference,重新包裝對象主要是爲了重設一下ResourceRelr,它會在釋放資源的時候減少Entry的clientCount,並將該緩存對象
加入到mExclusiveEntries中,mExclusiveEntries裏存放的是已經被使用過的緩存(等待被釋放),如果緩存對象可以釋放,則直接釋放緩存對象。 - 判斷是否需要釋放資源,當超過了EvictEntries最大容量或者緩存池已滿,則移除EvictEntries最早插入的對象。
獲取緩存
public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, MemoryTrimmable {
@Nullable
public CloseableReference<V> get(final K key) {
Preconditions.checkNotNull(key);
Entry<K, V> oldExclusive;
CloseableReference<V> clientRef = null;
synchronized (this) {
//1. 查詢該緩存,說明該緩存可能要被使用,則嘗試將其從待移除緩存集合移除。
oldExclusive = mExclusiveEntries.remove(key);
//2. 從緩存集合中查詢該緩存。
Entry<K, V> entry = mCachedEntries.get(key);
if (entry != null) {
//3. 如果查詢到該緩存,將該緩存對象包裝成一個CloseableReference,重新包裝對象主要是爲了重設
//一下ResourceReleaser,它會在釋放資源的時候減少Entry的clientCount,並將該緩存對象
// 加入到mExclusiveEntries中,mExclusiveEntries裏存放的是已經被使用過的緩存(等待被釋放),
// 如果緩存對象可以釋放,則直接釋放緩存對象。
clientRef = newClientReference(entry);
}
}
//4. 判斷是否需要通知待刪除集合裏的元素被移除了。
maybeNotifyExclusiveEntryRemoval(oldExclusive);
//5. 判斷是否需要更新緩存參數。
maybeUpdateCacheParams();
//6. 判斷是否需要釋放資源,當超過了EvictEntries最大容量或者緩存池已滿,則移除EvictEntries最早插入的對象。
maybeEvictEntries();
return clientRef;
}
}
獲取緩存主要執行了以下操作:
- 查詢該緩存,說明該緩存可能要被使用,則嘗試將其從待移除緩存集合移除。
- 從緩存集合中查詢該緩存。
- 如果查詢到該緩存,將該緩存對象包裝成一個CloseableReference,重新包裝對象主要是爲了重設一下ResourceReleaser,它會在釋放資源的時候減少Entry的clientCount,並將該緩存對象
加入到mExclusiveEntries中,mExclusiveEntries裏存放的是已經被使用過的緩存(等待被釋放),如果緩存對象可以釋放,則直接釋放緩存對象。 . 判斷是否需要通知待刪除集合裏的元素被移除了。 - 判斷是否需要更新緩存參數。
- 判斷是否需要釋放資源,當超過了EvictEntries最大容量或者緩存池已滿,則移除EvictEntries最早插入的對象。
移除緩存
移除緩存就是調用集合的removeAll()方法移除所有的元素,如下所示:
public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, MemoryTrimmable {
public int removeAll(Predicate<K> predicate) {
ArrayList<Entry<K, V>> oldExclusives;
ArrayList<Entry<K, V>> oldEntries;
synchronized (this) {
oldExclusives = mExclusiveEntries.removeAll(predicate);
oldEntries = mCachedEntries.removeAll(predicate);
makeOrphans(oldEntries);
}
maybeClose(oldEntries);
maybeNotifyExclusiveEntryRemoval(oldExclusives);
maybeUpdateCacheParams();
maybeEvictEntries();
return oldEntries.size();
}
}
這個方法比較簡單,我們重點關注的是一個多次出現的方法:maybeEvictEntries(),它是用來調節總緩存的大小的,保證緩存不超過最大緩存個數和最大容量,如下所示:
public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, MemoryTrimmable {
private void maybeEvictEntries() {
ArrayList<Entry<K, V>> oldEntries;
synchronized (this) {
int maxCount = Math.min(
//待移除集合最大持有的緩存個數
mMemoryCacheParams.maxEvictionQueueEntries,
//緩存集合最大持有的緩存個數 - 當前正在使用的緩存個數
mMemoryCacheParams.maxCacheEntries - getInUseCount());
int maxSize = Math.min(
//待移除集合最大持有的緩存容量
mMemoryCacheParams.maxEvictionQueueSize,
//緩存集合最大持有的緩存容量 - 當前正在使用的緩存容量
mMemoryCacheParams.maxCacheSize - getInUseSizeInBytes());
//1. 根據maxCount和maxSize,不斷的從mExclusiveEntries移除隊頭的元素,知道滿足緩存限制規則。
oldEntries = trimExclusivelyOwnedEntries(maxCount, maxSize);
//2. 將緩存Entry的isOrphan置爲true,表示該Entry對象不再被追蹤,等待被刪除。
makeOrphans(oldEntries);
}
//3. 關閉緩存。
maybeClose(oldEntries);
//4. 通知緩存被關閉。
maybeNotifyExclusiveEntryRemoval(oldEntries);
}
}
整個調整容量的流程就是根據當前緩存的個數和容量進行調整直到滿足最大緩存個數和最大緩存容量的限制,如下所示:
- 根據maxCount和maxSize,不斷的從mExclusiveEntries移除隊頭的元素,知道滿足緩存限制規則。
- 將緩存Entry的isOrphan置爲true,表示該Entry對象不再被追蹤,等待被刪除。
- 關閉緩存。
- 通知緩存被關閉。
以上就是內存緩存的全部內容,我們接着來看磁盤緩存的實現。:point_down:
3.2 磁盤緩存
我們前面已經說過,磁盤緩存也分爲三層,我們再來回顧一下,如下圖所示:
:point_right: 點擊圖片查看大圖
磁盤緩存因爲涉及到文件讀寫要比內存緩存複雜一些,從下至上可以將磁盤緩存分爲三層:
- 緩衝緩存層:由BufferedDiskCache實現,提供緩衝功能。
- 文件緩存層:由DiskStroageCache實現,提供實際的緩存功能。
- 文件存儲層:由DefaultDiskStorage實現,提供磁盤文件讀寫的功能。
我們來看看相關的接口。
磁盤緩存的接口是FileCache,如下所示:
public interface FileCache extends DiskTrimmable {
//是否可以進行磁盤緩存,主要是本地存儲是否存在以及是否可以讀寫。
boolean isEnabled();
//返回緩存的二進制資源
BinaryResource getResource(CacheKey key);
//是否包含該緩存key,異步調用。
boolean hasKeySync(CacheKey key);
//是否包含該緩存key,同步調用。
boolean hasKey(CacheKey key);
boolean probe(CacheKey key);
//插入緩存
BinaryResource insert(CacheKey key, WriterCallback writer) throws IOException;
//移除緩存
void remove(CacheKey key);
//獲取緩存總大小
long getSize();
//獲取緩存個數
long getCount();
//清除過期的緩存
long clearOldEntries(long cacheExpirationMs);
//清除所有緩存
void clearAll();
//獲取磁盤dump信息
DiskStorage.DiskDumpInfo getDumpInfo() throws IOException;
}
可以發現FileCahce接口繼承於DisTrimmable,它是一個用來監聽磁盤容量變化的接口,如下所示:
public interface DiskTrimmable {
//當磁盤只有很少的空間可以使用的時候回調。
void trimToMinimum();
//當磁盤沒有空間可以使用的時候回調
void trimToNothing();
}
除了緩存接口DiskStorageCache,Fresco還定義了DiskStorage接口來封裝文件IO的讀寫邏輯,如下所示:
public interface DiskStorage {
class DiskDumpInfoEntry {
public final String path;
public final String type;
public final float size;
public final String firstBits;
protected DiskDumpInfoEntry(String path, String type, float size, String firstBits) {
this.path = path;
this.type = type;
this.size = size;
this.firstBits = firstBits;
}
}
class DiskDumpInfo {
public List<DiskDumpInfoEntry> entries;
public Map<String, Integer> typeCounts;
public DiskDumpInfo() {
entries = new ArrayList<>();
typeCounts = new HashMap<>();
}
}
//文件存儲是否可用
boolean isEnabled();
//是否包含外部存儲
boolean isExternal();
//獲取文件描述符指向的文件
BinaryResource getResource(String resourceId, Object debugInfo) throws IOException;
//檢查是否包含文件描述符所指的文件
boolean contains(String resourceId, Object debugInfo) throws IOException;
//檢查resourceId對應的文件是否存在,如果存在則更新上次讀取的時間戳。
boolean touch(String resourceId, Object debugInfo) throws IOException;
void purgeUnexpectedResources();
//插入
Inserter insert(String resourceId, Object debugInfo) throws IOException;
//獲取磁盤緩存裏所有的Entry。
Collection<Entry> getEntries() throws IOException;
//移除指定的緩存Entry。
long remove(Entry entry) throws IOException;
//根據resourceId移除對應的磁盤緩存文件
long remove(String resourceId) throws IOException;
//清除所有緩存文件
void clearAll() throws IOException;
DiskDumpInfo getDumpInfo() throws IOException;
//獲取存儲名
String getStorageName();
interface Entry {
//ID
String getId();
//時間戳
long getTimestamp();
//大小
long getSize();
//Fresco使用BinaryResource對象來描述磁盤緩存對象,通過該對象可以獲取文件的輸入流、字節碼等信息。
BinaryResource getResource();
}
interface Inserter {
//寫入數據
void writeData(WriterCallback callback, Object debugInfo) throws IOException;
//提交寫入的數據
BinaryResource commit(Object debugInfo) throws IOException;
//取消此次插入操作
boolean cleanUp();
}
}
理解了主要接口的功能我們就看看看主要的實現類:
- DiskStroageCache:實現了FileCache接口與DiskTrimmable接口是緩存的主要實現類。
- DefaultDiskStorage:實現了DiskStorage接口,封裝了磁盤IO的讀寫邏輯。
- BufferedDiskCache:在DiskStroageCache的基礎上提供了Buffer功能。
BufferedDiskCache主要提供了三個方面的功能:
- 提供寫入緩衝StagingArea,所有要寫入的數據在發出寫入命令到最終寫入之前會存儲在這裏,在查找緩存的時候會首先在這塊區域內查找,若命中則直接返回;
- 提供了寫入數據的辦法,在writeToDiskCache中可以看出它提供的WriterCallback將要寫入的EncodedImage轉碼成輸入流;
- 將get、put兩個方法放在後臺線程中運行(get時在緩衝區域查找時除外),分別都是容量爲2的線程池。
我們來看看它們的實現細節。
上面DiskStorage裏定義了個接口Entry來描述磁盤緩存對象的信息,真正持有緩存對象的是BinaryResource接口,它的實現類是FileBinaryResource,該類主要定義了 File的一些操作,可以通過它獲取文件的輸入流和字節碼等。
此外,Fresco定義了每個文件的唯一描述符,此描述符由CacheKey的toString()方法導出字符串的SHA-1哈希碼,然後該哈希碼再經過Base64加密得出。
我們來看看磁盤緩存的插入、查找和刪除的實現。
插入緩存
public class DiskStorageCache implements FileCache, DiskTrimmable {
@Override
public BinaryResource insert(CacheKey key, WriterCallback callback) throws IOException {
//1. 先將磁盤緩存寫入到緩存文件,這可以提供寫緩存的併發速度。
SettableCacheEvent cacheEvent = SettableCacheEvent.obtain()
.setCacheKey(key);
mCacheEventListener.onWriteAttempt(cacheEvent);
String resourceId;
synchronized (mLock) {
//2. 獲取緩存的resoucesId。
resourceId = CacheKeyUtil.getFirstResourceId(key);
}
cacheEvent.setResourceId(resourceId);
try {
//3. 創建要插入的文件(同步操作),這裏構建了Inserter對象,該對象封裝了具體的寫入流程。
DiskStorage.Inserter inserter = startInsert(resourceId, key);
try {
inserter.writeData(callback, key);
//4. 提交新創建的緩存文件到緩存中。
BinaryResource resource = endInsert(inserter, key, resourceId);
cacheEvent.setItemSize(resource.size())
.setCacheSize(mCacheStats.getSize());
mCacheEventListener.onWriteSuccess(cacheEvent);
return resource;
} finally {
if (!inserter.cleanUp()) {
FLog.e(TAG, "Failed to delete temp file");
}
}
} catch (IOException ioe) {
//... 異常處理
} finally {
cacheEvent.recycle();
}
}
}
整個插入緩存的流程如下所示:
- 先將磁盤緩存寫入到緩存文件,這可以提供寫緩存的併發速度。
- 獲取緩存的resoucesId。
- 創建要插入的文件(同步操作),這裏構建了Inserter對象,該對象封裝了具體的寫入流程。
- 提交新創建的緩存文件到緩存中。
我們重點來看看這兩個方法startInsert()與endInsert()。
public class DiskStorageCache implements FileCache, DiskTrimmable {
//創建一個臨時文件,後綴爲.tmp
private DiskStorage.Inserter startInsert(
final String resourceId,
final CacheKey key)
throws IOException {
maybeEvictFilesInCacheDir();
//調用DefaultDiskStorage的insert()方法創建一個臨時文件
return mStorage.insert(resourceId, key);
}
//將緩存文件提交到緩存中,如何緩存文件已經存在則嘗試刪除原來的文件
private BinaryResource endInsert(
final DiskStorage.Inserter inserter,
final CacheKey key,
String resourceId) throws IOException {
synchronized (mLock) {
BinaryResource resource = inserter.commit(key);
//將resourceId添加點resourceId集合中,DiskStorageCache裏只維護了這一個集合
//來記錄緩存
mResourceIndex.add(resourceId);
mCacheStats.increment(resource.size(), 1);
return resource;
}
}
}
DiskStorageCache裏只維護了這一個集合Set
mResourceIndex來記錄緩存的Resource ID,而DefaultDiskStorage負責對磁盤上
的緩存就行管理,體爲DiskStorageCache提供索引功能。
我們接着來看看查找緩存的實現。
查找緩存
根據CacheKey查找緩存BinaryResource,如果緩存以及存在,則更新它的LRU訪問時間戳,如果緩存不存在,則返回空。
public class DiskStorageCache implements FileCache, DiskTrimmable {
@Override
public BinaryResource getResource(final CacheKey key) {
String resourceId = null;
SettableCacheEvent cacheEvent = SettableCacheEvent.obtain()
.setCacheKey(key);
try {
synchronized (mLock) {
BinaryResource resource = null;
//1. 獲取緩存的ResourceId,這裏是一個列表,因爲可能存在MultiCacheKey,它wrap多個CacheKey。
List<String> resourceIds = CacheKeyUtil.getResourceIds(key);
for (int i = 0; i < resourceIds.size(); i++) {
resourceId = resourceIds.get(i);
cacheEvent.setResourceId(resourceId);
//2. 獲取ResourceId對應的BinaryResource。
resource = mStorage.getResource(resourceId, key);
if (resource != null) {
break;
}
}
if (resource == null) {
//3. 緩存沒有命中,則執行onMiss()回調,並將resourceId從mResourceIndex移除。
mCacheEventListener.onMiss(cacheEvent);
mResourceIndex.remove(resourceId);
} else {
//4. 緩存命中,則執行onHit()回調,並將resourceId添加到mResourceIndex。
mCacheEventListener.onHit(cacheEvent);
mResourceIndex.add(resourceId);
}
return resource;
}
} catch (IOException ioe) {
//... 異常處理
return null;
} finally {
cacheEvent.recycle();
}
}
}
整個查找的流程如下所示:
- 獲取緩存的ResourceId,這裏是一個列表,因爲可能存在MultiCacheKey,它wrap多個CacheKey。
- 獲取ResourceId對應的BinaryResource。
- 緩存沒有命中,則執行onMiss()回調,並將resourceId從mResourceIndex移除。
- 緩存命中,則執行onHit()回調,並將resourceId添加到mResourceIndex。mCacheEventListener.onHit(cacheEvent);
這裏會調用DefaultDiskStorage的getReSource()方法去查詢緩存文件的路徑並構建一個BinaryResource對象。
Fresco在本地保存緩存文件的路徑如下所示:
parentPath + File.separator + resourceId + type;
parentPath是根目錄,type分爲兩種:
- private static final String CONTENT FILE EXTENSION = ".cnt";
- private static final String TEMP FILE EXTENSION = ".tmp";
以上就是查詢緩存的邏輯,我們接着來看看刪除緩存的邏輯。
刪除緩存
public class DiskStorageCache implements FileCache, DiskTrimmable {
@Override
public void remove(CacheKey key) {
synchronized (mLock) {
try {
String resourceId = null;
//獲取Resoucesid,根據resouceId移除緩存,並將自己從mResourceIndex移除。
List<String> resourceIds = CacheKeyUtil.getResourceIds(key);
for (int i = 0; i < resourceIds.size(); i++) {
resourceId = resourceIds.get(i);
mStorage.remove(resourceId);
mResourceIndex.remove(resourceId);
}
} catch (IOException e) {
//...移除處理
}
}
}
}
刪除緩存的邏輯也很簡單,獲取Resoucesid,根據resouceId移除緩存,並將自己從mResourceIndex移除。
磁盤緩存也會自己調節自己的緩存大小來滿足緩存最大容量限制條件,我們也來簡單看一看。
Fresco裏的磁盤緩存過載時,會以不超過緩存容量的90%爲目標進行清理,具體清理流程如下所示:
public class DiskStorageCache implements FileCache, DiskTrimmable {
@GuardedBy("mLock")
private void evictAboveSize(
long desiredSize,
CacheEventListener.EvictionReason reason) throws IOException {
Collection<DiskStorage.Entry> entries;
try {
//1. 獲取緩存目錄下所有文件的Entry的集合,以最近被訪問的時間爲序,最近被訪問的Entry放在後面。
entries = getSortedEntries(mStorage.getEntries());
} catch (IOException ioe) {
//... 捕獲異常
}
//要刪除的數據量
long cacheSizeBeforeClearance = mCacheStats.getSize();
long deleteSize = cacheSizeBeforeClearance - desiredSize;
//記錄刪除數據數量
int itemCount = 0;
//記錄刪除數據大小
long sumItemSizes = 0L;
//2. 循環遍歷,從頭部開始刪除元素,直到剩餘容量達到desiredSize位置。
for (DiskStorage.Entry entry: entries) {
if (sumItemSizes > (deleteSize)) {
break;
}
long deletedSize = mStorage.remove(entry);
mResourceIndex.remove(entry.getId());
if (deletedSize > 0) {
itemCount++;
sumItemSizes += deletedSize;
SettableCacheEvent cacheEvent = SettableCacheEvent.obtain()
.setResourceId(entry.getId())
.setEvictionReason(reason)
.setItemSize(deletedSize)
.setCacheSize(cacheSizeBeforeClearance - sumItemSizes)
.setCacheLimit(desiredSize);
mCacheEventListener.onEviction(cacheEvent);
cacheEvent.recycle();
}
}
//3. 更新容量,刪除不需要的臨時文件。
mCacheStats.increment(-sumItemSizes, -itemCount);
mStorage.purgeUnexpectedResources();
}
}
整個清理流程可以分爲以下幾步:
- 獲取緩存目錄下所有文件的Entry的集合,以最近被訪問的時間爲序,最近被訪問的Entry放在後面。
- 循環遍歷,從頭部開始刪除元素,直到剩餘容量達到desiredSize位置。
- 更新容量,刪除不需要的臨時文件。
關於Fresco的源碼分析就到這裏了,本來還想再講一講Fresco內存管理方面的知識,但是這牽扯到Java Heap以及Android匿名共享內存方面的知識,相對比較深入,所以 等着後續分析《Android內存管理框架》的時候結合着一塊講。
來自:http://www.open-open.com/lib/view/open1517706265842.html
Good luck!
Reprinted by Jimmy.li