關於做者java
郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。android
文章目錄git
更多Android開源框架源碼分析文章請參見Android open framework analysis。程序員
這個系列的文章原來叫作《Android開源框架源碼分析》,後來這些優秀開源庫的代碼看的多了,感受大佬們代碼寫的真真美如畫👍,因此就改名爲《Android開源框架源碼鑑賞》了。閒話 很少說,咱們進入正題,今天分析的開源庫是Fresco。github
Fresco是一個功能完善的圖片加載框架,在Android開發中有着普遍的應用,那麼它做爲一個圖片加載框架,有哪些特點讓它備受推崇呢?編程
好,又吹了一波Fresco(人家好像也不給廣告費T_T),可是光知道人家好並無用,咱們還須要爲何這麼好,怎麼實現的,往後在作咱們的框架的時候偷師一手,豈不美哉。 Fresco的源碼仍是比較多的,看起來會比較費勁,可是不怕,Android的系統源碼都被咱們啃下來了,還怕一個小小的Fresco嗎😎。要更好的去理解Fresco的實現,仍是要從 總體入手,瞭解它的模塊和層次劃分,層層推動,逐個理解,才能達到融會貫通的效果。設計模式
因爲Fresco比較大,咱們先來看一下它的總體結構,有個總體的把握,Fresco的總體架構以下圖所示:數組
👉 點擊圖片查看大圖緩存
縱觀整個Fresco的架構,DraweeView是門面,和用戶進行交互,DraweeHierarchy是視圖層級,管理圖層,DraweeController是控制器,管理數據。它們構成了整個Fresco框架的三駕馬車。固然還有咱們 幕後英雄Producer,全部的髒活累活都是它乾的,最佳勞模👍bash
理解了Fresco總體的架構,咱們還有了解在這套礦建裏發揮重要做用的幾個關鍵角色,以下所示:
注:Fresco源碼裏的類的名字都比較長,可是都是按照必定的命令規律來的,例如:以Supplier結尾的類都實現了Supplier接口,它能夠提供某一個類型的對象(factory, generator, builder, closure等)。 以Builder結尾的固然就是以構造者模式建立對象的類。
經過上面的描述,想必你們都Fresco有了一個總體的認識,那面對這樣龐大的一個庫,咱們在去分析它的時候須要重點關注哪些點呢?🤔
👉 注:Fresco裏還大量運用各類設計模式,例如:Builder、Factory、Wrapper、Producer/Consumer、Adapter等,在閱讀源碼的時候,你們也要留心這些設計模式的應用與實踐。
接下來咱們就帶着這4個問題去源碼中一探究竟。
至於分析的手段,仍是老套路,先從一個簡單的例子入手,展現Fresco是如何加載圖片的,而後去分析它的圖片加載流程,讓你們有個總體的理解,而後再逐個去分析Fresco每一個 子模塊的功能實現。
好,咱們先來寫一個小例子。
👉 舉例
初始化
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));
複製代碼
咱們來看一下它的調用流程,序列圖以下所示:
👉 點擊圖片查看大圖
嗯,圖看起來有點大,可是沒關係,咱們按照顏色將整個流程分爲了四大步:
👉 注:Fresco裏的類雖多,類名雖長,但都是基於接口和Abstract類的設計,每一個模塊自成一套繼承體系,因此只要掌握了它們的繼承關係以及不一樣模塊之間的聯繫,整個 流程仍是比較簡單的。
因爲序列圖設計具體細節,爲了輔助理解,咱們再提供一張總結新的流程圖,以下所示:
👉 點擊圖片查看大圖
接下來,咱們就針對這兩張圖結合具體細節來一一分析。
👉 序列圖 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在初始化的過程當中,主要作了三件事情:
這裏面咱們須要重點關注三個對象:
咱們先來看ImagePipelineConfig,ImagePipelineConfig經過建造者模式來構建傳遞給ImagePipeline的參數,以下所示:
上述參數基本不須要咱們手動配置,除非項目上有定製性的需求。
咱們能夠發現,在初始化方法的最後調用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;
}
}
複製代碼
能夠發如今這個方法裏初始化了兩個重要的對象:
這個PipelineDraweeControllerFactory就是用來構建PipelineDraweeController,咱們前面說過PipelineDraweeController繼承於AbstractDraweeController,用來控制圖片 數據的獲取和加載,這個PipelineDraweeControllerFactory()的init()方法也是將參數裏的遍歷傳入PipelineDraweeControllerFactory中,用來準備構建PipelineDraweeController。 咱們來看一下它都傳入哪些東西進去。
👉 注:所謂拔出蘿蔔帶出泥,在分析圖片加載流程的時候不免會帶進來各類各樣的類,若是一時理不清它們的關係也不要緊,第一步只是要掌握總體的加載流程便可,後面 咱們會對這些類逐一分析。
該方法執行完成後調用SimpleDraweeView的initizlize()方法將PipelineDraweeControllerBuilderSupplier對象設置進SimpleDraweeView的靜態對象sDraweeControllerBuilderSupplier中 整個初始化流程便完成了。
👉 序列圖 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類:
瞭解了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的構建,咱們來看看它的實現。
👉 注: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()的實現,以下所示:
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的更多內容,咱們後面會專門講,這個方法主要作了兩件事情:
能夠發現該方法最終調用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);
}
}
}
複製代碼
該方法主要作了三件事情:
接着CloseableProducerToDataSourceAdapter調用了本身create()方法構建一個CloseableProducerToDataSourceAdapter對象。至此DataSource已經完成完成了,而後把它設置到 PipelineDraweeController裏。
咱們接着來看綁定Controller與Hierarchy的流程。👇
👉 序列圖 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();
}
}
}
複製代碼
上述方法的流程也十分簡單,以下所示:
上述流程裏有兩個關鍵的地方:設置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的綁定流程就完成了。
👉 序列圖 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的圖片加載流程就完成了,面對如此長的流程,讀者難免疑惑,咱們只要掌握了總體流程,就能夠 分而治之,逐個擊破。
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:
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
容器類Drawable
視圖類Drawable
除了這些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上的圖層主要被分紅了如下幾層:
理解了圖層的層級構造,咱們接着來看看圖層的建立流程。👇
咱們前面說過在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對象,以下所示:
而構建的方法設計到兩個方法
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的過程當中都要應用相應的縮放類型和圓角角度,以下所示:
這樣一個圖層的載體GenericDraweeHierarchy就構建完成了,後續GenericDraweeHierarchy裏的各類操做都是調用器內部的各類Drawable的方法來完成的。
咱們前面說過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負責從網絡獲取數據。
緩存數據獲取類Producer,這類Producer負責從緩存中獲取數據。
功能類Producer,這類Producer在初始化的時候會傳入一個nextProducer,它們會對nextProducer產生的結果進行處理。
那麼這些Producer是在哪裏構建的呢?🤔
咱們前面說過,在構建DataSource的時候,會調用ProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);方法爲指定的ImageRequest構建 想要的Producer序列,事實上,ProducerSequenceFactory裏除了getDecodedImageProducerSequence()方法覺得,還有幾個針對其餘狀況獲取序列的方法,這裏咱們 列一下從網絡獲取圖片的時候Producer序列是什麼樣的。
以下所示:
咱們上面說道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裏有三級緩存,兩級內存緩存,一級磁盤緩存,以下圖所示:
👉 點擊圖片查看大圖
磁盤緩存由於涉及到文件讀寫要比內存緩存複雜一些,從下至上能夠將磁盤緩存分爲三層:
咱們先來看看Fresco的緩存鍵值的設計,Fresco爲緩存鍵設計了一個接口,以下所示:
public interface CacheKey {
String toString();
boolean equals(Object o);
int hashCode();
//是不是由Uri構建而來的
boolean containsUri(Uri uri);
//獲取url string
String getUriString();
}
複製代碼
CacheKey有兩個實現類:
好,咱們繼續來分析內存緩存和磁盤緩存的實現。
咱們前面說到,內存緩存分爲兩級:
它們的區別在於緩存的數據格式不一樣,未編碼圖片內存緩存使用的是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內部使用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;
}
複製代碼
👉 注:只有引用數量(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;
}
}
複製代碼
插入緩存主要作了如下幾件事情:
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;
}
}
複製代碼
獲取緩存主要執行了如下操做:
移除緩存就是調用集合的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);
}
}
複製代碼
整個調整容量的流程就是根據當前緩存的個數和容量進行調整直到知足最大緩存個數和最大緩存容量的限制,以下所示:
以上就是內存緩存的所有內容,咱們接着來看磁盤緩存的實現。👇
咱們前面已經說過,磁盤緩存也分爲三層,咱們再來回顧一下,以下圖所示:
👉 點擊圖片查看大圖
磁盤緩存由於涉及到文件讀寫要比內存緩存複雜一些,從下至上能夠將磁盤緩存分爲三層:
咱們來看看相關的接口。
磁盤緩存的接口是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();
}
}
複製代碼
理解了主要接口的功能咱們就看看看主要的實現類:
BufferedDiskCache主要提供了三個方面的功能:
咱們來看看它們的實現細節。
上面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();
}
}
}
複製代碼
整個插入緩存的流程以下所示:
咱們重點來看看這兩個方法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();
}
}
}
複製代碼
整個查找的流程以下所示:
這裏會調用DefaultDiskStorage的getReSource()方法去查詢緩存文件的路徑並構建一個BinaryResource對象。
Fresco在本地保存緩存文件的路徑以下所示:
parentPath + File.separator + resourceId + type;
複製代碼
parentPath是根目錄,type分爲兩種:
以上就是查詢緩存的邏輯,咱們接着來看看刪除緩存的邏輯。
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();
}
}
複製代碼
整個清理流程能夠分爲如下幾步:
關於Fresco的源碼分析就到這裏了,原本還想再講一講Fresco內存管理方面的知識,可是這牽扯到Java Heap以及Android匿名共享內存方面的知識,相對比較深刻,因此 等着後續分析《Android內存管理框架》的時候結合着一塊講。