在前面的文章 Fresco 原始碼分析 —— 圖片載入流程 裡面詳細說明了圖片載入的整個流程,但是除了理解原始碼之外,對於原始碼的框架層面的設計也是需要去了解的,不能只是簡單的讀原始碼,好的原始碼的框架設計也是值得我們去學習的。以後,我們自己在開發一個原始碼的時候,也就能將學到的好的經驗運用到自己的程式碼上。
程式碼工程
從 module 層面來看,可以看到 Fresco 是有很多 module 的,並且這些 module 都是按照每個 module 的功能進行劃分的,可以通過名字就可以知道 module 的作用。
雖然 module 很多,但是並不像我們平時的專案裡面一樣,一個 module 包含很多程式碼,有的可能只有幾個類,只是 fresco 整體分的比較細。
下面介紹下一些關鍵的 module:
-
animated-base:主要都是動圖的一些共有的基礎操作,包闊每一幀的快取,解碼渲染,幀數,寬高等邏輯。
-
Drawee:就是 UI 層,例如 DraweeView, drawable 相關的類等;
-
imagepipeline:整個工程的核心,圖片載入,記憶體、快取管理,bitmap處理等等核心邏輯;
-
imagepipeline-base:是 imagepipeline 的一些基礎類,包括介面(快取,解碼,圖片資訊)以及相關的基礎類;
-
imagepipeline-backends :這裡就是最後的請求邏輯,這裡給出兩個不同的示例,分別採用 volly 和 ok-http 來實現的,預設是 HttpUrlConnectionNetworkFetcher ,也就是說業務方有著更復雜的業務需求的話,需要自己去實現。
-
Drawee-backends:主要是在 Drawee 的基礎上封裝了請求和初始化邏輯,比如 Fresco,PipelineDraweeController 等相關類。
設計思想淺談
1、裡面有很多類以 config 結尾,他們是怎麼用的?
ImagePipelineConfig:內部採用 builder 模式來進行建立,主要是把 ImagePipeline 需要的引數都通過 config 來進行管理;
ImagePipelineFactory: 通過 ImagePipelineConfig 來生成 ImagePipeline ;
類似的還有:
-
DraweeConfig: 內部採用 builder 模式來進行建立,包含 Drawee 相關的配置;
-
DiskCacheConfig:內部採用 builder 模式來進行建立,包含 disk 的各種配置,包括目錄,版本,快取大小,錯誤日誌等。最後也是使用 DiskStorageCacheFactory 來生成 disk;
- PoolConfig:內部採用 builder 模式來進行建立,其實最終也是再 PoolFactory 裡面使用;
可以發現,Fresco 裡面的 config 類都是採用了 buidler 模式。那為啥需要採用 builder 模式呢?因為 config 從名字來說是配置類,裡面會有很多引數,所以會採用 builder 模式,以後別人在配置的時候,就不會關心過的引數問題。
其實對於內部 builder 我有個疑問就是為啥要使用內部 builder 。
-
一是內部屬性太多,如果採用建構函式模式,那需要寫很多建構函式
-
二是採用builder 模式後,使用者只需要設定他關心的屬性,其他不關心的屬性都可以採用預設值來進行處理,也就是減輕了使用者的壓力。
-
三是一旦構造完成,就不可以修改了,builder 裡面都是設定屬性,但是類本身只提供獲取屬性方法,不提供設定方法,隔絕了使用者更改帶來的不可控因素。
-
至於內部builder 可能是不希望將他們獨立出去,散落在各處不好管理。
2、提供了很多 producer,consumer,那這麼多類是如何管理的,他們之間惡關係如何維護。
提供了 ProducerFactory 來管理所有的 producer。ProducerFactory 有靜態方法,大多數是非靜態方法。主要用來獲取各種 producer 。
ProducerSequenceFactory: 這個其實就是把各個 Producer 連線在一起;或者說是按照一定的規則,將他們組裝在一起。這樣外界在呼叫的時候,只需要確定你的 Sequence 是什麼樣的,呼叫對應的方法 獲取 Sequence。其中在 Sequence 裡面又會通過 ProducerFactory 來獲取指定的
每一個 producer 又會有一個對應的 consumer,可以發現大多數 consumer 都是 producer 裡面的內部類。
3、DataSource 的作用
DataSource 是一個泛型介面。按照原始碼的描述,它和 future 原理差不多,但是有個不一樣的地方,就是它可以獲取當前的進度。
AbstractDataSource 繼承自 DataSource;這塊內部已經維護好了各種狀態;然後會通過 listeners 進行通知。
AbstractProducerToDataSourceAdapter : 繼承自 AbstractDataSource,從名字就可以看出來這是一個介面卡,將 Producer 轉為 DataSource。
4、ProducerContext 的作用
主要是用來將上下文資訊傳遞給 Producer;可以具體看看程式碼,可以發現 context 內部包含很多邏輯。
public interface ProducerContext { @StringDef({ ExtraKeys.ORIGIN, ExtraKeys.ORIGIN_SUBCATEGORY, ExtraKeys.NORMALIZED_URI, ExtraKeys.SOURCE_URI, ExtraKeys.ENCODED_WIDTH, ExtraKeys.ENCODED_HEIGHT, ExtraKeys.ENCODED_SIZE, ExtraKeys.MULTIPLEX_BITMAP_COUNT, ExtraKeys.MULTIPLEX_ENCODED_COUNT, }) @interface ExtraKeys { String ORIGIN = "origin"; String ORIGIN_SUBCATEGORY = "origin_sub"; String SOURCE_URI = "uri_source"; String NORMALIZED_URI = "uri_norm"; String ENCODED_WIDTH = "encoded_width"; String ENCODED_HEIGHT = "encoded_height"; String ENCODED_SIZE = "encoded_size"; /* number of deduped request in BitmapMemoryCacheKeyMultiplexProducer */ String MULTIPLEX_BITMAP_COUNT = "multiplex_bmp_cnt"; /* number of deduped request in EncodedCacheKeyMultiplexProducer */ String MULTIPLEX_ENCODED_COUNT = "multiplex_enc_cnt"; } /** @return image request that is being executed */ ImageRequest getImageRequest(); /** @return id of this request */ String getId(); /** @return optional id of the UI component requesting the image */ @Nullable String getUiComponentId(); /** @return ProducerListener2 for producer's events */ ProducerListener2 getProducerListener(); /** @return the {@link Object} that indicates the caller's context */ Object getCallerContext(); /** @return the lowest permitted {@link ImageRequest.RequestLevel} */ ImageRequest.RequestLevel getLowestPermittedRequestLevel(); /** @return true if the request is a prefetch, false otherwise. */ boolean isPrefetch(); /** @return priority of the request. */ Priority getPriority(); /** @return true if request's owner expects intermediate results */ boolean isIntermediateResultExpected(); /** * Adds callbacks to the set of callbacks that are executed at various points during the * processing of a request. * * @param callbacks callbacks to be executed */ void addCallbacks(ProducerContextCallbacks callbacks); ImagePipelineConfig getImagePipelineConfig(); EncodedImageOrigin getEncodedImageOrigin(); void setEncodedImageOrigin(EncodedImageOrigin encodedImageOrigin); <E> void setExtra(@ExtraKeys String key, @Nullable E value); void putExtras(@Nullable Map<String, ?> extras); @Nullable <E> E getExtra(String key); @Nullable <E> E getExtra(String key, @Nullable E valueIfNotFound); Map<String, Object> getExtras(); /** Helper to set {@link ExtraKeys#ORIGIN} and {@link ExtraKeys#ORIGIN_SUBCATEGORY} */ void putOriginExtra(@Nullable String origin, @Nullable String subcategory); /** Helper to set {@link ExtraKeys#ORIGIN} */ void putOriginExtra(@Nullable String origin); }
這裡說下 ProducerContext 使用,這裡其實採用的是介面模式,然後相當於是面向介面程式設計,這樣後期在擴充套件的時候,也會變得更加方便。
類似的上下文還有 FrescoContext。
其實對於上下文,我們在設計原始碼的時候,也可以考慮下,有了 context 的存在,可以減少很多類之間的依賴,使得程式碼邏輯更加清晰。
5、ImagePipeline
ImagePipelineConfig: 這個可以說是把 ImagePipeline 用到的東西一網打盡;這個是用於使用者在使用 Fresco 的時候,可以進行對應的配置。
ImagePipeline: 發起請求的類(包括網路,本地快取,記憶體,回撥)以及解碼和非解碼的圖片,還有包括預取;
ImagePipelineFactory: 是一個單例。也就是說明所有的請求配置是一樣的。但是這樣的話,怎麼區分不同的請求,這塊還需要仔細看看的。url 是通過 imageRequest 來管理的,ImagePipeline 主要是負責管理其他方面的東西。包括快取等所有請求應該都是一樣的。然後在獲取 getDataSourceSupplier 的時候發起圖片請求。
imageRequest:imageRequest 包含 url 等相關資訊。會在 ImagePipeline 中構造一個 producerSequence。最終,producerSequence 和 settableProducerContext 會在 AbstractProducerToDataSourceAdapter 轉變為 DataSource;
6、ImagePipeline 和 producer 之間的關係
這個其實在 ImagePipeline 中解釋過了,他們相當於是一環套一環。
7、builder 模式的使用
ImageRequestBuilder: 用於構建 ImageRequest。
AbstractDraweeControllerBuilder: 使用得是泛型,將通用邏輯封裝在其內部;
PipelineDraweeControllerBuilder:controller 的邏輯在裡面;
這個其實主要是在前面的 config 裡面講過了,這裡就不再細說。
8、factory 模式的應用
DefaultDrawableFactory:生成動態靜態圖片;
PipelineDraweeControllerFactory:一個是建立controller,一個是建立內部controller;
ImagePipelineFactory:這個有點感覺是個容器,所有和 ImagePipeline 相關的都可以從裡面獲取到;
其他 factory 的類也有很多。就我個人理解,之所以用到 factory ,主要是為了遮蔽產品的具體實現,呼叫者只關心產品的介面。