最近因為對項目的圖片庫做了功能拓展和優化,花了點時間研究了下Glide,輸出了總共6篇解析文章:
圖片框架 - Glide 4.11.0源碼走讀
圖片框架 - Glide自定義配置和組件及Registry機制
圖片框架 - Glide加載webp動圖流程解析
圖片框架 - Glide解碼webp動圖淺析
圖片框架 - Glide緩存機制解析
圖片框架 - Glide磁盤緩存研究
本篇文章對整個Glide源碼宏觀剖析做一個簡單總結。因為項目是基于Glide4.8.0,所以方便起見,分析的源碼也是4.8.0版本。為了閱讀方便,文章就盡量不貼對應代碼了,Glide代碼量確實有點多,想了解詳情的可以參考上面的6篇文章,里面有詳細源碼解析。
一、Glide整體結構
1.1整個Glide主干功能:
1.2 圖片加載整體執行流程:
Glide作為外部調用的入口函數,主要收集請求參數,構建一個圖片請求,交由engine去獲取圖片資源,engine先從內存獲取,如果活躍資源中有直接拿,如果沒有則嘗試去lruCache中獲取,如果也沒有則通過EngineJob線程池,發起一個異步任務即:DecodeJob來進行磁盤和網絡獲取。這里磁盤緩存策略會根據DiskCacheStrategy來設置,它主要是配置對圖片原始數據流緩存以及解碼轉碼后資源緩存兩種類型數據。Generator是具體的圖片資源獲取管理類,它經由ModelLoader-LoadData-具體Fetcher的層層內部類調用關系最終交由對應的Fetcher類去處理圖片資源獲取的任務。成功獲取資源后,會層層回調回來到DecodeJob來做解碼工作(如果是原始資源),解碼通過ModelLoader-LoadPath-具體Decoder的層層內部類調用關系最終交由對應的Decoder去做圖片資源解碼任務。后續就是將圖片設置到目標控件上去。 這里,DecodeJob本身是通過Stage來調度自身不同任務類型,另外,網絡IO和本地IO是分屬于不同的DecodeJob,也就是兩者任務的轉換是需要通過EngineJob切線程來完成的。最后,Registry支持用戶自定義配置和組件。
1.3 圖片加載中數據轉換流程
一目了然,就不過多解釋了。
二、Glide核心類圖
這里簡單例舉了部分核心類之間的關系:
-
Glide
: 入口類。 -
RequestBuilder
: 收集參數,構建Request和Target交由RequestManager去統一處理。 -
RequestManager
:左膀右臂:TargetTracker負責Target對應頁面生命周期綁定、RequestTracker負責發起Request請求。 -
Engine
:加載引擎。 -
EngineJob
:線程池。 -
DcodeJob
:異步任務,負責圖片獲取和解碼。 -
Generator
:負責獲取圖片資源,這里分了三類(ResourceCache對應解碼后緩存、DataCache對應原始資源緩存、Source對應網絡請求),通過ModelLoader最終匹配到對應的Fetcher來執行具體獲取圖片資源任務。 -
Target
:顯示圖片的目標控件。
三、相關執行流程
3.1 首先看Glide.with(this).load(url).into(imageView) 整體調用流程
3.1.1 with
with主要干兩件事:
圖片加載綁定對應頁面生命周期;
生命周期分為application 和 非application兩種。分別通過ApplicationLifecycle、ActivityFragmentLifecycle來管理生命周期。初始化RequestManager;
3.1.2 load
load主要干一件事情:
- 通過RequestManager初始化RequestBuilder。收集model和requestOption相關請求參數,為后續into封裝request做準備。
3.1.3 into
into主要干了三件事:
- 封裝并發起request。
- request獲取圖片數據。
- 將圖片顯示到View上。
3.2 Glide整體緩存機制
3.2.1內存加載數據邏輯
3.2.2 內存、磁盤、網絡請求整體存取邏輯
這里簡單總結下:
取邏輯:
內存 > 磁盤 > 網絡請求
內存:
上次剛被加載的資源(activeResources) > (最近被加載的資源)lruCache。磁盤:
如果有主動設置DiskCacheStrategy,則按設置來。如果配置的是DiskCacheStrategy.ALL:則是取轉換之后的資源(ResourceCache) > (DataCache)原始資源網絡請求:
走網絡請求獲取圖片資源流。
存邏輯:
內存:
當前被加載的圖片資源存到activeResources中,下次加載資源切換時,當前activeResources會remove然后被轉移到lruCache。磁盤:
網絡請求成功之后,獲取到資源流,然后看DiskCacheStrategy是否支持磁盤緩存,如果支持,會通過回調在SourceGenerator中通過cacheData進行資源緩存。
3.3 Glide磁盤緩存流程
這里有兩個關鍵節點:原始數據流緩存和解碼后資源緩存。SourceGenerator本身除了發起網絡請求之外,也會在網絡請求成功后,在DiskCacheStrategy允許的條件下對原始數據進行磁盤緩存,其次在DecodeJob數據解碼成功后,在DiskCacheStrategy允許的條件下對解碼后的資源進行磁盤緩存。
這里有個問題:如果網絡請求成功后緩存的圖片原始數據流本身有問題,解碼失敗的話,框架頂多是不會緩存解碼后資源,但是對有問題的原始數據緩存不會做處理,這就會導致后續獲取圖片時按優先級優先獲取有問題的圖片原始數據緩存,導致問題。
解決分析:
客戶端層面:首先客戶端無法單獨判斷是否是解碼失敗,因為Glide網絡請求失敗、解碼失敗、IO失敗等等都統一拋的是onLoadFailed,其次對外暴露的api也沒有單獨清理一張圖片的,只有批量清理磁盤緩存,可以批量清,但這樣會影響到整體性能。也可以通過加signature繞過去,但是這樣每次都會去請求網絡,相當于不走緩存,最后也可以調整DiskCacheStrategy配置,不做原始數據緩存了,這個倒是可以。
解決:我本身也不太喜歡直接gradle引三方庫,這樣一來不好拓展功能、二來不好定位修復問題。如果開源,我一般都會引入源碼,所以這里我直接改了源碼:
DecodeJob.java
private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Retrieved data", startFetchTime,
"data: " + currentData
+ ", cache key: " + currentSourceKey
+ ", fetcher: " + currentFetcher);
}
Resource<R> resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
//解碼失敗,刪除之前緩存磁盤的原始數據文件
diskCacheProvider.getDiskCache().delete(new DataCacheKey(currentSourceKey, signature));
runGenerators();
}
}
具體分析過程參考文章:圖片框架 - Glide磁盤緩存研究
好了,經過上面的圖文并茂的解析,應該對Glide不管是整體還是部分都有了一個比較直觀的了解了。
四、Glide中編譯時注解+APT的應用
最后再簡單介紹下Glide中編譯時注解+APT的應用。
注解的玩法主要有兩個場景:運行期和編譯期。
- 運行期:主要是注解+反射,注解提供標簽,反射對注解類來做邏輯。
- 編譯期:注解+APT+反射,這里APT(Annotation Processor Tool)注解處理器,編譯期會動態生成類,而類的內容要么自己手動拼串組裝類內容,要么使用JavaPoet封裝好的工具來組裝類內容。
而Glide中,單例調用get()初始化時:
private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules(Context context) {
GeneratedAppGlideModule result = null;
try {
Class<GeneratedAppGlideModule> clazz =
(Class<GeneratedAppGlideModule>)
Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
result =
clazz.getDeclaredConstructor(Context.class).newInstance(context.getApplicationContext());
} catch (ClassNotFoundException e) {
...
}
return result;
}
這里的com.bumptech.glide.GeneratedAppGlideModuleImpl就是APT動態生成的:
源碼對應的Processor路徑:
這里就不詳細分析GeneratedAppGlideModuleImpl的生成了,它最終的功能是針對manifest 和 注解兩種注冊方式分別調用其applyOptions和registerComponents來觸發自定義配置和組件。
Demo地址:https://github.com/Zhto0/GlideWebpDemo
好了就寫這么多吧,Glide總體來說還是比較復雜的,本篇文章主要是對Glide做一個宏觀分析,以及工作中牽涉到的部分功能進行了淺析。在這個宏觀了解的基礎上,應該能夠對Glide框架局部問題的定位和分析提供一點幫助。當然文章中如有不對地方,歡迎批評指正!