原文: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2018/0403/9555.html
這里添加了一些自己的閱讀筆記.
版本 4.x
一弟晚、前言
在眾多的圖片加載框架中,Glide是Google推薦的,并在自家的項(xiàng)目中大量使用的一個非常強(qiáng)大的框架舶赔,專注于平滑滾動,并且還提供Gif俩莽,本地Vedio首幀的解碼和顯示息裸。Glide提供了非常便捷的鏈?zhǔn)秸{(diào)用接口,以及豐富的拓展和自定義功能骂际,開發(fā)者可以非常簡單地對框架進(jìn)行配置和圖片再加工。
如今Gilde已經(jīng)更新到4.x冈欢,了解其源碼對更好的使用Glide歉铝,以及學(xué)習(xí)相關(guān)的圖片處理技術(shù),學(xué)習(xí)更優(yōu)雅的編碼會有很大的幫助凑耻。
再github上放了一個最簡單的工程, 可以直接拿來研究代碼
https://github.com/shaopx/GlideApp
當(dāng)然你要是不嫌麻煩可以去官方地址: 要想正常編譯還是要費(fèi)點(diǎn)勁的.
https://github.com/bumptech/glide
不得不說太示,Glide整個框架的極其復(fù)雜的柠贤,特別是在對資源的轉(zhuǎn)換和解碼過程中,涉及了許多的嵌套循環(huán)类缤,同時也使用了大量的工廠模式用于生產(chǎn)轉(zhuǎn)換模塊臼勉,編碼模塊,解碼模塊等餐弱,筆者在閱讀過程中宴霸,多次迷失在茫茫的代碼流中。
為此膏蚓,萌生了將對Glide的理解記錄成文的想法瓢谢,借以理清思路,也希望這一系列的文章可以幫助到無論是了解驮瞧,還是準(zhǔn)備閱讀Glide源碼的你氓扛,稍微理清一些思路。如有不對的地方论笔,歡迎指正~
那么接下來采郎,我們就先看看Glide是如何進(jìn)行框架初始化的。
注意:本文源碼版本為v4.6.1狂魔,不同版本可能存在一些差異蒜埋!
二、Glide.with發(fā)生了什么最楷?
1. Glide單例的加載
使用過Glide的都知道理茎,調(diào)用Glide加載一張圖片時,第一句代碼便是Glide.with(this)管嬉,這里肯定就是Glide的入口了皂林,通過這句代碼,Glide開始了“漫漫的”初始化之路蚯撩。
Glide重載了多個with的方法础倍,分別用于不同的情境下使用,我們看其中最常用的在Activity中調(diào)用的方法胎挎,即
首先沟启,跟進(jìn)getRetriever(activity)
這里首先檢查了context是否為空,如果為null犹菇,拋出異常德迹。
我們重點(diǎn)來看Glide.get(context)
這里是一個典型的雙檢鎖單例模式。
繼續(xù)跟進(jìn)checkAndInitialzeGlide(context)
注意這里揭芍,在最后注入了一個GlideBuilder胳搞,這個就是Glide的建造器,用于構(gòu)建Glide的一些參數(shù)和配置。
最后肌毅,來到真正初始化Glide的方法(代碼去除了一些Log打涌曜)。
留意最后將初始化得到的glide賦值給了Glide.glide的單例悬而。
接下里就來看看在這初始化方法中呜舒,Glide都加載了哪些配置。
2. GlideModule配置加載
在使用Glide的時候笨奠,我們都會有一些想要設(shè)置的系統(tǒng)級配置袭蝗,如設(shè)置緩存的存儲位置,緩存區(qū)的大小般婆,網(wǎng)絡(luò)加載模塊等等呻袭,那么我們通常就是使用GldieModule進(jìn)行配置。在Glide3.x中腺兴,我們首先會定義一個繼承于GlideModule的類,然后在項(xiàng)目的AndroidMenifest.xml中進(jìn)行指定:
1. `<meta-data android:name="com.test.GlideConfiguration"`
2. `android:value="GlideModule"/>`
而在Glide4中廉侧,提供另外一個配置的模式页响,那就是注解,并且不再繼承GlideModule段誊,而是繼承AppGlideModule和LibraryGlideModule闰蚕,分別對應(yīng)Application和Library,使用@GlideModule注解進(jìn)行標(biāo)記连舍。而Glide3.x中的配置方式已經(jīng)建議放棄使用没陡。
@GlideModule
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//設(shè)置緩存到外部存儲器
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context));
}
}
那么,Glide是如何對GlideModule的配置進(jìn)行初始化的呢索赏?
第二行代碼中盼玄,getAnnotationGeneratedGlideModules()會獲取Glide注解自動生產(chǎn)的一個Glide的Module配置器。如下:
其中‘com.bumptech.glide.GeneratedAppGlideModuleImpl’是在編譯時由Glide生成的一個類潜腻,主要用于過濾不必要的GlideModule埃儿,以及提供一個請求檢索器工廠,這個后面會講到融涣。
接下生成一個Manifest解析器ManifestParser童番,用于獲取配置的GlideModule,并存放在manifestModules中威鹿。然后是一個判斷
if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
......
}
如果條件成立剃斧,即編譯時自動生成的類中,包含了需要排除的GlideModule忽你,逐個將其移除幼东。
接著以上代碼,Glide將逐個調(diào)用剩下的GlideModule,并回調(diào)applyOptions和registerComponents接口筋粗,這時策橘,用戶配置的GlideModule就會被調(diào)用,同時用戶設(shè)置的參數(shù)也就被配置到Glide中娜亿。
在以上代碼中丽已,發(fā)現(xiàn)一句代碼,在回調(diào)registerComponents前买决,首先構(gòu)建了glide的實(shí)例沛婴。
這是一句非常重要的代碼,整個Glide框架最重要的初始化內(nèi)容都在其中實(shí)現(xiàn)督赤。
1. `Glide glide = builder.build(applicationContext);`
3. GlideBuilder構(gòu)建Glide單例
跳轉(zhuǎn)到GlideBuilder中嘁灯,看build方法做了哪些事情。代碼并不復(fù)雜躲舌,直接看代碼中的注釋丑婿。
通過以上一系列工具的新建,Glide建立了資源請求線程池没卸,本地緩存加載線程池羹奉,動畫線程池,內(nèi)存緩存器约计,磁盤緩存工具等等诀拭,接著構(gòu)造了Engine數(shù)據(jù)加載引擎,最后再將Engine注入Glide煤蚌,構(gòu)建Glide耕挨。
其中還建立了一個請求器索引器,用于索引RequestManger尉桩,后面我們再詳細(xì)講筒占。
我們進(jìn)入最后, 構(gòu)建Glide。
4. 構(gòu)建Glide蜘犁,配置數(shù)據(jù)轉(zhuǎn)換器/解碼器/轉(zhuǎn)碼器/編碼器
回到Glide中赋铝,看看Glide的構(gòu)造函數(shù),這是一個長得變態(tài)的構(gòu)造函數(shù)(有200行)沽瘦,但是不必被它嚇倒(好吧革骨,其實(shí)第一次看到這里,我是被嚇倒了析恋,直接略過去了良哲,限于文章篇幅,這里只截取了部分源碼助隧,仔細(xì)的話可以直接看源碼)筑凫,仔細(xì)分析一下滑沧,其實(shí)整個構(gòu)造過程并沒那么復(fù)雜。
其中最重要的是步驟3和步驟4巍实,分別為Glide初始化了模型轉(zhuǎn)換加載器滓技,解碼器,轉(zhuǎn)碼器棚潦,編碼器令漂,并將對各種類型進(jìn)行一一注冊,將其列成表格如下:
- 模型轉(zhuǎn)換器
轉(zhuǎn)換器 | 功能 |
---|---|
ResourceLoader.StreamFactory | 將Android資源ID轉(zhuǎn)換為Uri丸边,在加載成為InputStream |
ResourceLoader.UriFactory | 將資源ID轉(zhuǎn)換為Uri |
ResourceLoader.FileDescriptorFactory | 將資源ID轉(zhuǎn)化為ParcelFileDescriptor |
ResourceLoader.AssetFileDescriptorFactory | 將資源ID轉(zhuǎn)化為AssetFileDescriptor |
UnitModelLoader.Factory | 不做任何轉(zhuǎn)換叠必,返回源數(shù)據(jù) |
ByteBufferFileLoader.Factory | 將File轉(zhuǎn)換為ByteBuffer |
FileLoader.StreamFactory | 將File轉(zhuǎn)換為InputStream |
FileLoader.FileDescriptorFactory | 將File轉(zhuǎn)化為ParcelFileDescriptor |
DataUrlLoader.StreamFactory | 將Url轉(zhuǎn)化為InputStream |
StringLoader.StreamFactory | 將String轉(zhuǎn)換為InputStream |
StringLoader.AssetFileDescriptorFactory | 將String轉(zhuǎn)換為AssetFileDescriptor |
HttpUriLoader.Factory | 將http/https Uri轉(zhuǎn)換為InputStream |
UriLoader.StreamFactory | 將Uri轉(zhuǎn)換為InputStream |
UriLoader.FileDescriptorFactory | 將Uri轉(zhuǎn)換為ParcelFileDescriptor |
UriLoader.AssetFileDescriptorFactory | 將Uri轉(zhuǎn)換為AssetFileDescriptor |
UrlUriLoader.StreamFactory | 將將http/https的Uri轉(zhuǎn)換為InputStream |
UrlLoader.StreamFactory | 將Url轉(zhuǎn)換為InputStream |
HttpGlideUrlLoader.Factory | 將HttpGlide轉(zhuǎn)換為InputStream |
...... | ...... |
- 解碼器
解碼器 | 功能 |
---|---|
ByteBufferGifDecoder | 將ByteBuffer解碼為GifDrawable |
ByteBufferBitmapDecoder | 將ByteBuffer解碼為Bitmap |
ResourceDrawableDecoder | 將資源Uri解碼為Drawable |
ResourceBitmapDecoder | 將資源ID解碼為Bitmap |
BitmapDrawableDecoder | 將數(shù)據(jù)解碼為BitmapDrawable |
StreamBitmapDecoder | 將InputStreams解碼為Bitmap |
StreamGifDecoder | 將InputStream數(shù)據(jù)轉(zhuǎn)換為BtyeBuffer,再解碼為GifDrawable |
GifFrameResourceDecoder | 解碼gif幀 |
FileDecoder | 包裝File成為FileResource |
UnitDrawableDecoder | 將Drawable包裝為DrawableResource |
UnitBitmapDecoder | 包裝Bitmap成為BitmapResource |
VideoDecoder | 將本地視頻文件解碼為Bitmap |
- 轉(zhuǎn)碼器
轉(zhuǎn)碼器 | 功能 |
---|---|
BitmapDrawableTranscoder | 將Bitmap轉(zhuǎn)碼為BitmapDrawable |
BitmapBytesTranscoder | 將Bitmap轉(zhuǎn)碼為Byte arrays |
DrawableBytesTranscoder | 將BitmapDrawable轉(zhuǎn)碼為Byte arrays |
GifDrawableBytesTranscoder | 將GifDrawable轉(zhuǎn)碼為Byte arrays |
- 編碼器
編碼器 | 功能 |
---|---|
ByteBufferEncoder | 將Byte數(shù)據(jù)緩存為File |
StreamEncoder | InputStream緩存為File |
BitmapEncoder | 將Bitmap數(shù)據(jù)緩存為File |
BitmapDrawableEncoder | 將BitmapDrawable數(shù)據(jù)緩存為File |
GifDrawableEncoder | 將GifDrawable數(shù)據(jù)緩存為File |
- 模型轉(zhuǎn)換注冊表(實(shí)在太多妹窖,只列出了部分)
源數(shù)據(jù) | 轉(zhuǎn)換數(shù)據(jù) | 轉(zhuǎn)換器 |
---|---|---|
Integer.class | InputStream.class | ResourceLoader.StreamFactory |
Integer.class | ParcelFileDescriptor.class | ResourceLoader.FileDescriptorFactory |
...... | ...... | ...... |
String.class | InputStream.class | DataUrlLoader.StreamFactory |
String.class | InputStream.class | StringLoader.StreamFactory |
...... | ...... | ...... |
Uri.class | InputStream.class | DataUrlLoader.StreamFactory |
Uri.class | InputStream.class | HttpUriLoader.Factory |
Uri.class | InputStream.class | UriLoader.StreamFactory |
URL.class | InputStream.class | UrlLoader.StreamFactory |
...... | ...... | ...... |
以上模型轉(zhuǎn)換注冊表非常重要纬朝,在Glide進(jìn)入解碼流程時,將會遍歷這里注冊的所有可能轉(zhuǎn)換的情形骄呼,嘗試進(jìn)行數(shù)據(jù)轉(zhuǎn)換共苛。
這里只列出部分情形,其它還包括File/Bitmap/Drawable/Byte等等幾乎涵括了日常使用的情況蜓萄。
Glide的加載流程可以概括為以下流程:
model(數(shù)據(jù)源)-->data(轉(zhuǎn)換數(shù)據(jù))-->decode(解碼)-->transformed(縮放)-->transcoded(轉(zhuǎn)碼)-->encoded(編碼保存到本地)
其中隅茎,transformed為對解碼得到的圖片數(shù)據(jù)進(jìn)行縮放,如FitCenter绕德、CropCenter等。
到這里摊阀,Glide單例就構(gòu)建完成了耻蛇,讓我們返回到Glide#with中
在構(gòu)建好Glide后,通過getRequestManagerRetriever()將會得到一個RequestManagerRetriever胞此,即RequestManager的檢索器臣咖,RequestManagerRetriever#get()將為每個請求頁面創(chuàng)建一個RequestManager。
還記得GlideBuilder#build提到的一句代碼嗎漱牵?
1. `RequestManagerRetriever requestManagerRetriever =`
2. `new RequestManagerRetriever(requestManagerFactory);`
沒錯夺蛇,這里獲取的就是它。這里就必須要講到Glide數(shù)據(jù)請求的生命周期了酣胀。
我們都知道Glide會根據(jù)頁面的生命周期來自動的開啟和結(jié)束數(shù)據(jù)的請求刁赦,那么Glide是怎么做到的呢?
5. 生命周期管理
我們進(jìn)入RequestManagerRetriever#get(Activity)方法中闻镶。
首先甚脉,判斷是否為后臺線程其爵,如果是八酒,則使用ApplicationContext重新獲取。
重點(diǎn)來看else代碼塊草娜。先斷言請求的頁面是否已經(jīng)銷毀。否則獲取當(dāng)前頁面的FragmentManager猴凹,并傳給fragmentGet方法夷狰。
在fragmentGet中首先通過getRequestManagerFragment()來獲取一個命名為FRAGMENT_TAG的fragment,如不存在郊霎,則新建一個RequestManagerFragment沼头,并添加到當(dāng)前頁面中。
這里我們就可以猜到了歹篓,Glide是通過在頁面中添加一個Fragment來動態(tài)監(jiān)聽頁面的創(chuàng)建和銷毀瘫证,從而達(dá)到依賴頁面生命周期,動態(tài)管理請求的目的庄撮。
在RequestManagerFragment構(gòu)造函數(shù)中背捌,注入了一個生命周期監(jiān)聽器ActivityFragmentLifecycle,并在Fragment各個生命周期回調(diào)中洞斯,調(diào)用了對應(yīng)的方法毡庆。
而ActivityFragmentLifecycle也緊接著會調(diào)用lifecycleListener監(jiān)聽器,而這個監(jiān)聽器其實(shí)就是RequestManger烙如。如下:
最后么抗,RequestManagerRetriever#fragmentGet,判斷這個Fragment的RequestManager是否存在亚铁,否則創(chuàng)建一個RequestManager蝇刀,并將生命周期注入,同時RquestManager構(gòu)建時徘溢,將會通過addListener注入生命周期回調(diào)(具體可以查看RequestManger構(gòu)造函數(shù))吞琐。
最后,Glide#with終將得到一個RequestManager然爆。
至此站粟,Glide的加載過程就解析完畢了≡瘢總結(jié)一下整個流程:
- 通過AndroidManifest和@GlideModule注解獲取用戶自定義配置GlideModule奴烙,并調(diào)用其對應(yīng)的方法
- 通過GlideBuilder構(gòu)建Glide:
1.新建線程池
2.新建圖片緩存池和緩存池
3.新建內(nèi)存緩存管理器
4.新建默認(rèn)本地緩存管理器
5.新建請求引擎Engine
6.新建RequestManger檢索器
7.新建Glide - Glide構(gòu)造方法中,新建模型轉(zhuǎn)換器剖张,解碼器切诀,轉(zhuǎn)碼器,編碼器搔弄,以及生成Glide上下文GlideContext
- 通過RequestManager檢索器趾牧,建立生命周期監(jiān)聽,并建立一個RequestManager
- 完成肯污!
三翘单、 Glide與GlideApp
如果在項(xiàng)目中已經(jīng)使用了Glide3.x吨枉,并且想要升級到Glide4.x,那么你會發(fā)現(xiàn)哄芜,原來使用鏈?zhǔn)秸{(diào)用進(jìn)行參數(shù)配置的方法已經(jīng)被修改了貌亭,同一個封裝到了RequesOptions中,如下:
RequestOptions options = new RequestOptions()
.centerCrop()
.placeholder(R.mipmap.ic_launcher_round)
.error(R.mipmap.ic_launcher)
.priority(Priority.HIGH)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(ImageConfig.URL_GIF)
.apply(options)
.into(iv);
這樣的話升級后將導(dǎo)致大量的修改认臊,當(dāng)然你也可以自己封裝一下圃庭,但是Glide已經(jīng)為我們做好了兼容方案。
還記得初始化是通過@GlideModule注解來注冊自定義配置嗎失晴?只要在項(xiàng)目中定義這么一個配置剧腻,那么Glide將會自動幫我們生成一個GlideApp模塊,封裝了Glide3.x中的調(diào)用方式涂屁。
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
}
調(diào)用如下书在,還是原來的配方,還是熟悉的味道~
GlideApp.with(this)
.load(ImageConfig.URL_WEBP)
.sizeMultiplier(0.5f)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.error(R.mipmap.ic_launcher)
.into(iv);
如果你還覺得不爽拆又,那么你甚至可以把GlideApp直接修改為Glide儒旬,實(shí)現(xiàn)幾乎“無縫對接”。當(dāng)然帖族,你還是要修改引用路徑的栈源。
@GlideModule(glideName="Glide")
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
}
以上,就是Glide4初始化的源碼解析了