Android開源框架源碼鑑賞:Fresco

關於做者java

郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。android

文章目錄git

  • 一 圖片加載流程
    • 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開源框架源碼分析》,後來這些優秀開源庫的代碼看的多了,感受大佬們代碼寫的真真美如畫👍,因此就改名爲《Android開源框架源碼鑑賞》了。閒話 很少說,咱們進入正題,今天分析的開源庫是Fresco。github

Fresco是一個功能完善的圖片加載框架,在Android開發中有着普遍的應用,那麼它做爲一個圖片加載框架,有哪些特點讓它備受推崇呢?編程

  • 完善的內存管理功能,減小圖片對內存的佔用,即使在低端機器上也有着不錯的表現。
  • 自定義圖片加載的過程,能夠先顯示低清晰度圖片或者縮略圖,加載完成後再顯示高清圖,能夠在加載的時候縮放和旋轉圖片。
  • 自定義圖片繪製的過程,能夠自定義谷中焦點、圓角圖、佔位圖、overlay、進圖條。
  • 漸進式顯示圖片。
  • 支持Gif。
  • 支持Webp。

好,又吹了一波Fresco(人家好像也不給廣告費T_T),可是光知道人家好並無用,咱們還須要爲何這麼好,怎麼實現的,往後在作咱們的框架的時候偷師一手,豈不美哉。 Fresco的源碼仍是比較多的,看起來會比較費勁,可是不怕,Android的系統源碼都被咱們啃下來了,還怕一個小小的Fresco嗎😎。要更好的去理解Fresco的實現,仍是要從 總體入手,瞭解它的模塊和層次劃分,層層推動,逐個理解,才能達到融會貫通的效果。設計模式

因爲Fresco比較大,咱們先來看一下它的總體結構,有個總體的把握,Fresco的總體架構以下圖所示:數組

👉 點擊圖片查看大圖緩存

  • 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,全部的髒活累活都是它乾的,最佳勞模👍bash

理解了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流那樣,能夠一層嵌套一層,最終只獲得一個結果,這是一個很精巧的設計👍
  • Consumer:用來接收Producer產生的結果,它與Producer組成了生產者與消費者模式。

注:Fresco源碼裏的類的名字都比較長,可是都是按照必定的命令規律來的,例如:以Supplier結尾的類都實現了Supplier接口,它能夠提供某一個類型的對象(factory, generator, builder, closure等)。 以Builder結尾的固然就是以構造者模式建立對象的類。

經過上面的描述,想必你們都Fresco有了一個總體的認識,那面對這樣龐大的一個庫,咱們在去分析它的時候須要重點關注哪些點呢?🤔

  1. 圖片加載流程
  2. DraweeController與DraweeHierarchy
  3. Producer與Consumer
  4. 緩存機制

👉 注: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));
複製代碼

咱們來看一下它的調用流程,序列圖以下所示:

👉 點擊圖片查看大圖

嗯,圖看起來有點大,可是沒關係,咱們按照顏色將整個流程分爲了四大步:

  1. 初始化Fresco。
  2. 獲取DataSource。
  3. 綁定Controller與Hierarchy。
  4. 從內存緩存/磁盤緩存/網絡獲取圖片,並設置到對應的Drawable層。

👉 注:Fresco裏的類雖多,類名雖長,但都是基於接口和Abstract類的設計,每一個模塊自成一套繼承體系,因此只要掌握了它們的繼承關係以及不一樣模塊之間的聯繫,整個 流程仍是比較簡單的。

因爲序列圖設計具體細節,爲了輔助理解,咱們再提供一張總結新的流程圖,以下所示:

👉 點擊圖片查看大圖

接下來,咱們就針對這兩張圖結合具體細節來一一分析。

1.1 初始化Fresco

👉 序列圖 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在初始化的過程當中,主要作了三件事情:

  1. 加載so庫,這個主要是一些第三方的native庫,例如:giflib,libjpeg,libpng,主要用來作圖片解碼。
  2. 設置傳入的配置參數magePipelineConfig。
  3. 初始化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;
      }
}
複製代碼

能夠發如今這個方法裏初始化了兩個重要的對象:

  1. 獲取ImagePipeline。
  2. 獲取PipelineDraweeControllerFactory,並初始化。

這個PipelineDraweeControllerFactory就是用來構建PipelineDraweeController,咱們前面說過PipelineDraweeController繼承於AbstractDraweeController,用來控制圖片 數據的獲取和加載,這個PipelineDraweeControllerFactory()的init()方法也是將參數裏的遍歷傳入PipelineDraweeControllerFactory中,用來準備構建PipelineDraweeController。 咱們來看一下它都傳入哪些東西進去。

  • context.getResources():Android的Resources對象。
  • DeferredReleaser.getInstance():延遲釋放資源,等主線程處理完消息後再進行回收。
  • mImagePipeline.getBitmapMemoryCache():已解碼的圖片緩存。

👉 注:所謂拔出蘿蔔帶出泥,在分析圖片加載流程的時候不免會帶進來各類各樣的類,若是一時理不清它們的關係也不要緊,第一步只是要掌握總體的加載流程便可,後面 咱們會對這些類逐一分析。

該方法執行完成後調用SimpleDraweeView的initizlize()方法將PipelineDraweeControllerBuilderSupplier對象設置進SimpleDraweeView的靜態對象sDraweeControllerBuilderSupplier中 整個初始化流程便完成了。

1.2 獲取DataSource

👉 序列圖 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的構建,咱們來看看它的實現。

👉 注: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的更多內容,咱們後面會專門講,這個方法主要作了兩件事情:

  1. 獲取Producer序列,爲DataSource提供不一樣的數據輸入管道,Producer是由不少種的,表明從不一樣途徑獲取圖片數據,咱們下面會詳細講。
  2. 調用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);
        }
      }
}
複製代碼

該方法主要作了三件事情:

  1. 獲取緩存級別,RequestLevel將緩存分爲四級 FULL_FETCH(1) 從網絡或者本地存儲獲取,DISK_CACHE(2) 從磁盤緩存獲取,ENCODED_MEMORY_CACHE(3)從 未接嗎的內存緩存獲取,BITMAP_MEMORY_CACHE(4)已解碼的內存緩存獲取。
  2. 將ImageRequest、RequestListener等信息封裝進SettableProducerContext,ProducerContext是Producer 的上下文環境,利用ProducerContext能夠改變Producer內部的狀態。
  3. 建立CloseableProducerToDataSourceAdapter,CloseableProducerToDataSourceAdapter是DataSource的一種。

接着CloseableProducerToDataSourceAdapter調用了本身create()方法構建一個CloseableProducerToDataSourceAdapter對象。至此DataSource已經完成完成了,而後把它設置到 PipelineDraweeController裏。

咱們接着來看綁定Controller與Hierarchy的流程。👇

1.3 綁定DraweeController與DraweeHierarchy

👉 序列圖 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();
        }
      }
 }
複製代碼

上述方法的流程也十分簡單,以下所示:

  1. 若是已經和Controller創建聯繫,則先detach。
  2. 清楚舊的Controller。
  3. 爲Controller從新設置Hierarchy)創建新的Controller。
  4. 對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層

👉 序列圖 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)

理解了圖層的層級構造,咱們接着來看看圖層的建立流程。👇

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對象,以下所示:

  1. 構建背景圖層Drawable。
  2. 構建佔位圖層Drawable。
  3. 構建加載的圖片圖層Drawable。
  4. 構建進度條圖層Drawable。
  5. 構建從新加載的圖片圖層Drawable。
  6. 構建失敗圖片圖層Drawable。
  7. 構建疊加圖圖層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序列是什麼樣的。

以下所示:

  1. PostprocessedBitmapMemoryCacheProducer,非必須 ,在Bitmap緩存中查找被PostProcess過的數據。
  2. PostprocessorProducer,非必須,對下層Producer傳上來的數據進行PostProcess。
  3. BitmapMemoryCacheGetProducer,必須,使Producer序列只讀。
  4. ThreadHandoffProducer,必須,使下層Producer工做在後臺進程中執行。
  5. BitmapMemoryCacheKeyMultiplexProducer,必須,使多個相同已解碼內存緩存鍵的ImageRequest都從相同Producer中獲取數據。
  6. BitmapMemoryCacheProducer,必須,從已解碼的內存緩存中獲取數據。
  7. DecodeProducer,必須,將下層Producer產生的數據解碼。
  8. ResizeAndRotateProducer,非必須,將下層Producer產生的數據變換。
  9. EncodedCacheKeyMultiplexProducer,必須,使多個相同未解碼內存緩存鍵的ImageRequest都從相同Producer中獲取數據。
  10. EncodedMemoryCacheProducer,必須,從未解碼的內存緩存中獲取數據。
  11. DiskCacheProducer,必須,從文件緩存中獲取數據。
  12. WebpTranscodeProducer,非必須,將下層Producer產生的Webp(若是是的話)進行解碼。
  13. 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裏有三級緩存,兩級內存緩存,一級磁盤緩存,以下圖所示:

👉 點擊圖片查看大圖

  • 未編碼圖片內存緩存
  • 已編碼圖片內存緩存
  • 磁盤緩存

磁盤緩存由於涉及到文件讀寫要比內存緩存複雜一些,從下至上能夠將磁盤緩存分爲三層:

  • 緩衝緩存層:由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;
}
複製代碼

👉 注:只有引用數量(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;
      }
}
複製代碼

插入緩存主要作了如下幾件事情:

  1. 檢查是否須要更新緩存參數。
  2. 在緩存中查找要插入的對象,若存在則將其從待移除緩存集合移除,並調用它的close()方法當該緩存對象的引用數目爲0的時候會釋放掉該對象。
  3. 檢查是否緩存對象達到最大顯示或者緩存池已滿,若是都爲否,則插入新緩存對象。
  4. 將插入的對象包裝成一個CloseableReference,從新包裝對象主要是爲了重設一下ResourceRelr,它會在釋放資源的時候減小Entry的clientCount,並將該緩存對象 加入到mExclusiveEntries中,mExclusiveEntries裏存放的是已經被使用過的緩存(等待被釋放),若是緩存對象能夠釋放,則直接釋放緩存對象。
  5. 判斷是否須要釋放資源,當超過了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;
      }

}
複製代碼

獲取緩存主要執行了如下操做:

  1. 查詢該緩存,說明該緩存可能要被使用,則嘗試將其從待移除緩存集合移除。
  2. 從緩存集合中查詢該緩存。
  3. 若是查詢到該緩存,將該緩存對象包裝成一個CloseableReference,從新包裝對象主要是爲了重設一下ResourceReleaser,它會在釋放資源的時候減小Entry的clientCount,並將該緩存對象 加入到mExclusiveEntries中,mExclusiveEntries裏存放的是已經被使用過的緩存(等待被釋放),若是緩存對象能夠釋放,則直接釋放緩存對象。 . 判斷是否須要通知待刪除集合裏的元素被移除了。
  4. 判斷是否須要更新緩存參數。
  5. 判斷是否須要釋放資源,當超過了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);
      }

}
複製代碼

整個調整容量的流程就是根據當前緩存的個數和容量進行調整直到知足最大緩存個數和最大緩存容量的限制,以下所示:

  1. 根據maxCount和maxSize,不斷的從mExclusiveEntries移除隊頭的元素,知道知足緩存限制規則。
  2. 將緩存Entry的isOrphan置爲true,表示該Entry對象再也不被追蹤,等待被刪除。
  3. 關閉緩存。
  4. 通知緩存被關閉。

以上就是內存緩存的所有內容,咱們接着來看磁盤緩存的實現。👇

3.2 磁盤緩存

咱們前面已經說過,磁盤緩存也分爲三層,咱們再來回顧一下,以下圖所示:

👉 點擊圖片查看大圖

磁盤緩存由於涉及到文件讀寫要比內存緩存複雜一些,從下至上能夠將磁盤緩存分爲三層:

  • 緩衝緩存層:由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();
       }
     } 
}
複製代碼

整個插入緩存的流程以下所示:

  1. 先將磁盤緩存寫入到緩存文件,這能夠提供寫緩存的併發速度。
  2. 獲取緩存的resoucesId。
  3. 建立要插入的文件(同步操做),這裏構建了Inserter對象,該對象封裝了具體的寫入流程。
  4. 提交新建立的緩存文件到緩存中。

咱們重點來看看這兩個方法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();
       }
     } 
}
複製代碼

整個查找的流程以下所示:

  1. 獲取緩存的ResourceId,這裏是一個列表,由於可能存在MultiCacheKey,它wrap多個CacheKey。
  2. 獲取ResourceId對應的BinaryResource。
  3. 緩存沒有命中,則執行onMiss()回調,並將resourceId從mResourceIndex移除。
  4. 緩存命中,則執行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();
      }
}
複製代碼

整個清理流程能夠分爲如下幾步:

  1. 獲取緩存目錄下全部文件的Entry的集合,以最近被訪問的時間爲序,最近被訪問的Entry放在後面。
  2. 循環遍歷,從頭部開始刪除元素,直到剩餘容量達到desiredSize位置。
  3. 更新容量,刪除不須要的臨時文件。

關於Fresco的源碼分析就到這裏了,原本還想再講一講Fresco內存管理方面的知識,可是這牽扯到Java Heap以及Android匿名共享內存方面的知識,相對比較深刻,因此 等着後續分析《Android內存管理框架》的時候結合着一塊講。

相關文章
相關標籤/搜索