最近因?yàn)閷?xiàng)目的圖片庫做了功能拓展和優(yōu)化,花了點(diǎn)時(shí)間研究了下Glide,輸出了總共6篇解析文章:
圖片框架 - Glide 4.11.0源碼走讀
圖片框架 - Glide自定義配置和組件及Registry機(jī)制
圖片框架 - Glide加載webp動(dòng)圖流程解析
圖片框架 - Glide解碼webp動(dòng)圖淺析
圖片框架 - Glide緩存機(jī)制解析
圖片框架 - Glide磁盤緩存研究
本篇文章對整個(gè)Glide源碼宏觀剖析做一個(gè)簡單總結(jié)。因?yàn)轫?xiàng)目是基于Glide4.8.0,所以方便起見,分析的源碼也是4.8.0版本。為了閱讀方便,文章就盡量不貼對應(yīng)代碼了,Glide代碼量確實(shí)有點(diǎn)多,想了解詳情的可以參考上面的6篇文章,里面有詳細(xì)源碼解析。
一、Glide整體結(jié)構(gòu)
1.1整個(gè)Glide主干功能:

1.2 圖片加載整體執(zhí)行流程:

Glide作為外部調(diào)用的入口函數(shù),主要收集請求參數(shù),構(gòu)建一個(gè)圖片請求,交由engine去獲取圖片資源,engine先從內(nèi)存獲取,如果活躍資源中有直接拿,如果沒有則嘗試去lruCache中獲取,如果也沒有則通過EngineJob線程池,發(fā)起一個(gè)異步任務(wù)即:DecodeJob來進(jìn)行磁盤和網(wǎng)絡(luò)獲取。這里磁盤緩存策略會(huì)根據(jù)DiskCacheStrategy來設(shè)置,它主要是配置對圖片原始數(shù)據(jù)流緩存以及解碼轉(zhuǎn)碼后資源緩存兩種類型數(shù)據(jù)。Generator是具體的圖片資源獲取管理類,它經(jīng)由ModelLoader-LoadData-具體Fetcher的層層內(nèi)部類調(diào)用關(guān)系最終交由對應(yīng)的Fetcher類去處理圖片資源獲取的任務(wù)。成功獲取資源后,會(huì)層層回調(diào)回來到DecodeJob來做解碼工作(如果是原始資源),解碼通過ModelLoader-LoadPath-具體Decoder的層層內(nèi)部類調(diào)用關(guān)系最終交由對應(yīng)的Decoder去做圖片資源解碼任務(wù)。后續(xù)就是將圖片設(shè)置到目標(biāo)控件上去。 這里,DecodeJob本身是通過Stage來調(diào)度自身不同任務(wù)類型,另外,網(wǎng)絡(luò)IO和本地IO是分屬于不同的DecodeJob,也就是兩者任務(wù)的轉(zhuǎn)換是需要通過EngineJob切線程來完成的。最后,Registry支持用戶自定義配置和組件。
1.3 圖片加載中數(shù)據(jù)轉(zhuǎn)換流程

一目了然,就不過多解釋了。
二、Glide核心類圖

這里簡單例舉了部分核心類之間的關(guān)系:
-
Glide: 入口類。 -
RequestBuilder: 收集參數(shù),構(gòu)建Request和Target交由RequestManager去統(tǒng)一處理。 -
RequestManager:左膀右臂:TargetTracker負(fù)責(zé)Target對應(yīng)頁面生命周期綁定、RequestTracker負(fù)責(zé)發(fā)起Request請求。 -
Engine:加載引擎。 -
EngineJob:線程池。 -
DcodeJob:異步任務(wù),負(fù)責(zé)圖片獲取和解碼。 -
Generator:負(fù)責(zé)獲取圖片資源,這里分了三類(ResourceCache對應(yīng)解碼后緩存、DataCache對應(yīng)原始資源緩存、Source對應(yīng)網(wǎng)絡(luò)請求),通過ModelLoader最終匹配到對應(yīng)的Fetcher來執(zhí)行具體獲取圖片資源任務(wù)。 -
Target:顯示圖片的目標(biāo)控件。
三、相關(guān)執(zhí)行流程
3.1 首先看Glide.with(this).load(url).into(imageView) 整體調(diào)用流程
3.1.1 with

with主要干兩件事:
圖片加載綁定對應(yīng)頁面生命周期;
生命周期分為application 和 非application兩種。分別通過ApplicationLifecycle、ActivityFragmentLifecycle來管理生命周期。初始化RequestManager;
3.1.2 load

load主要干一件事情:
- 通過RequestManager初始化RequestBuilder。收集model和requestOption相關(guān)請求參數(shù),為后續(xù)into封裝request做準(zhǔn)備。
3.1.3 into

into主要干了三件事:
- 封裝并發(fā)起request。
- request獲取圖片數(shù)據(jù)。
- 將圖片顯示到View上。
3.2 Glide整體緩存機(jī)制
3.2.1內(nèi)存加載數(shù)據(jù)邏輯

3.2.2 內(nèi)存、磁盤、網(wǎng)絡(luò)請求整體存取邏輯

這里簡單總結(jié)下:
取邏輯:
內(nèi)存 > 磁盤 > 網(wǎng)絡(luò)請求
內(nèi)存:
上次剛被加載的資源(activeResources) > (最近被加載的資源)lruCache。磁盤:
如果有主動(dòng)設(shè)置DiskCacheStrategy,則按設(shè)置來。如果配置的是DiskCacheStrategy.ALL:則是取轉(zhuǎn)換之后的資源(ResourceCache) > (DataCache)原始資源網(wǎng)絡(luò)請求:
走網(wǎng)絡(luò)請求獲取圖片資源流。
存邏輯:
內(nèi)存:
當(dāng)前被加載的圖片資源存到activeResources中,下次加載資源切換時(shí),當(dāng)前activeResources會(huì)remove然后被轉(zhuǎn)移到lruCache。磁盤:
網(wǎng)絡(luò)請求成功之后,獲取到資源流,然后看DiskCacheStrategy是否支持磁盤緩存,如果支持,會(huì)通過回調(diào)在SourceGenerator中通過cacheData進(jìn)行資源緩存。
3.3 Glide磁盤緩存流程

這里有兩個(gè)關(guān)鍵節(jié)點(diǎn):原始數(shù)據(jù)流緩存和解碼后資源緩存。SourceGenerator本身除了發(fā)起網(wǎng)絡(luò)請求之外,也會(huì)在網(wǎng)絡(luò)請求成功后,在DiskCacheStrategy允許的條件下對原始數(shù)據(jù)進(jìn)行磁盤緩存,其次在DecodeJob數(shù)據(jù)解碼成功后,在DiskCacheStrategy允許的條件下對解碼后的資源進(jìn)行磁盤緩存。
這里有個(gè)問題:如果網(wǎng)絡(luò)請求成功后緩存的圖片原始數(shù)據(jù)流本身有問題,解碼失敗的話,框架頂多是不會(huì)緩存解碼后資源,但是對有問題的原始數(shù)據(jù)緩存不會(huì)做處理,這就會(huì)導(dǎo)致后續(xù)獲取圖片時(shí)按優(yōu)先級優(yōu)先獲取有問題的圖片原始數(shù)據(jù)緩存,導(dǎo)致問題。
解決分析:
客戶端層面:首先客戶端無法單獨(dú)判斷是否是解碼失敗,因?yàn)镚lide網(wǎng)絡(luò)請求失敗、解碼失敗、IO失敗等等都統(tǒng)一拋的是onLoadFailed,其次對外暴露的api也沒有單獨(dú)清理一張圖片的,只有批量清理磁盤緩存,可以批量清,但這樣會(huì)影響到整體性能。也可以通過加signature繞過去,但是這樣每次都會(huì)去請求網(wǎng)絡(luò),相當(dāng)于不走緩存,最后也可以調(diào)整DiskCacheStrategy配置,不做原始數(shù)據(jù)緩存了,這個(gè)倒是可以。
解決:我本身也不太喜歡直接gradle引三方庫,這樣一來不好拓展功能、二來不好定位修復(fù)問題。如果開源,我一般都會(huì)引入源碼,所以這里我直接改了源碼:
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 {
//解碼失敗,刪除之前緩存磁盤的原始數(shù)據(jù)文件
diskCacheProvider.getDiskCache().delete(new DataCacheKey(currentSourceKey, signature));
runGenerators();
}
}
具體分析過程參考文章:圖片框架 - Glide磁盤緩存研究
好了,經(jīng)過上面的圖文并茂的解析,應(yīng)該對Glide不管是整體還是部分都有了一個(gè)比較直觀的了解了。
四、Glide中編譯時(shí)注解+APT的應(yīng)用
最后再簡單介紹下Glide中編譯時(shí)注解+APT的應(yīng)用。
注解的玩法主要有兩個(gè)場景:運(yùn)行期和編譯期。
- 運(yùn)行期:主要是注解+反射,注解提供標(biāo)簽,反射對注解類來做邏輯。
- 編譯期:注解+APT+反射,這里APT(Annotation Processor Tool)注解處理器,編譯期會(huì)動(dòng)態(tài)生成類,而類的內(nèi)容要么自己手動(dòng)拼串組裝類內(nèi)容,要么使用JavaPoet封裝好的工具來組裝類內(nèi)容。
而Glide中,單例調(diào)用get()初始化時(shí):
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動(dòng)態(tài)生成的:

源碼對應(yīng)的Processor路徑:

這里就不詳細(xì)分析GeneratedAppGlideModuleImpl的生成了,它最終的功能是針對manifest 和 注解兩種注冊方式分別調(diào)用其applyOptions和registerComponents來觸發(fā)自定義配置和組件。
Demo地址:https://github.com/Zhto0/GlideWebpDemo
好了就寫這么多吧,Glide總體來說還是比較復(fù)雜的,本篇文章主要是對Glide做一個(gè)宏觀分析,以及工作中牽涉到的部分功能進(jìn)行了淺析。在這個(gè)宏觀了解的基礎(chǔ)上,應(yīng)該能夠?qū)lide框架局部問題的定位和分析提供一點(diǎn)幫助。當(dāng)然文章中如有不對地方,歡迎批評指正!