在前面的文章 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 ,主要是為了屏蔽產品的具體實現,調用者只關心產品的接口。