Glide4源碼解析系列
[Glide4源碼解析系列]–1.Glide初始化
[Glide4源碼解析系列]–2.Glide數據模型轉換與數據抓取
一、前言
在衆多的圖片加載框架中,Glide是Google推薦的,並在自家的項目中大量使用的一個非常強大的框架,專注於平滑滾動,並且還提供Gif,本地Vedio首幀的解碼和顯示。Glide提供了非常便捷的鏈式調用接口,以及豐富的拓展和自定義功能,開發者可以非常簡單地對框架進行配置和圖片再加工。
如今Gilde已經更新到4.x,瞭解其源碼對更好的使用Glide,以及學習相關的圖片處理技術,學習更優雅的編碼會有很大的幫助。
不得不說,Glide整個框架的極其複雜的,特別是在對資源的轉換和解碼過程中,涉及了許多的嵌套循環,同時也使用了大量的工廠模式用於生產轉換模塊,編碼模塊,解碼模塊等,筆者在閱讀過程中,多次迷失在茫茫的代碼流中。
爲此,萌生了將對Glide的理解記錄成文的想法,藉以理清思路,也希望這一系列的文章可以幫助到無論是瞭解,還是準備閱讀Glide源碼的你,稍微理清一些思路。如有不對的地方,歡迎指正~
那麼接下來,我們就先看看Glide是如何進行框架初始化的。
注意:本文源碼版本爲v4.6.1,不同版本可能存在一些差異!
二、Glide.with發生了什麼?
1. Glide單例的加載
使用過Glide的都知道,調用Glide加載一張圖片時,第一句代碼便是Glide.with(this),這裏肯定就是Glide的入口了,通過這句代碼,Glide開始了“漫漫的”初始化之路。
Glide重載了多個with的方法,分別用於不同的情境下使用,我們看其中最常用的在Activity中調用的方法,即
首先,跟進getRetriever(activity)
這裏首先檢查了context是否爲空,如果爲null,拋出異常。
我們重點來看Glide.get(context)
這裏是一個典型的雙檢鎖單例模式。
繼續跟進checkAndInitialzeGlide(context)
注意這裏,在最後注入了一個GlideBuilder,這個就是Glide的建造器,用於構建Glide的一些參數和配置。
最後,來到真正初始化Glide的方法(代碼去除了一些Log打印)。
留意最後將初始化得到的glide賦值給了Glide.glide的單例。
接下里就來看看在這初始化方法中,Glide都加載了哪些配置。
2. GlideModule配置加載
在使用Glide的時候,我們都會有一些想要設置的系統級配置,如設置緩存的存儲位置,緩存區的大小,網絡加載模塊等等,那麼我們通常就是使用GldieModule進行配置。在Glide3.x中,我們首先會定義一個繼承於GlideModule的類,然後在項目的AndroidMenifest.xml中進行指定:
<meta-data android:name="com.test.GlideConfiguration"
android:value="GlideModule"/>
而在Glide4中,提供另外一個配置的模式,那就是註解,並且不再繼承GlideModule,而是繼承AppGlideModule和LibraryGlideModule,分別對應Application和Library,使用@GlideModule註解進行標記。而Glide3.x中的配置方式已經建議放棄使用。
@GlideModule
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//設置緩存到外部存儲器
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context));
}
}
那麼,Glide是如何對GlideModule的配置進行初始化的呢?
第二行代碼中,getAnnotationGeneratedGlideModules()會獲取Glide註解自動生產的一個Glide的Module配置器。如下:
其中‘com.bumptech.glide.GeneratedAppGlideModuleImpl’是在編譯時由Glide生成的一個類,主要用於過濾不必要的GlideModule,以及提供一個請求檢索器工廠,這個後面會講到。
接下生成一個Manifest解析器ManifestParser,用於獲取配置的GlideModule,並存放在manifestModules中。然後是一個判斷
if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
......
}
如果條件成立,即編譯時自動生成的類中,包含了需要排除的GlideModule,逐個將其移除。
接着以上代碼,Glide將逐個調用剩下的GlideModule,並回調applyOptions和registerComponents接口,這時,用戶配置的GlideModule就會被調用,同時用戶設置的參數也就被配置到Glide中。
在以上代碼中,發現一句代碼,在回調registerComponents前,首先構建了glide的實例。
這是一句非常重要的代碼,整個Glide框架最重要的初始化內容都在其中實現。
Glide glide = builder.build(applicationContext);
3. GlideBuilder構建Glide單例
跳轉到GlideBuilder中,看build方法做了哪些事情。代碼並不複雜,直接看代碼中的註釋。
通過以上一系列工具的新建,Glide建立了資源請求線程池,本地緩存加載線程池,動畫線程池,內存緩存器,磁盤緩存工具等等,接着構造了Engine數據加載引擎,最後再將Engine注入Glide,構建Glide。
其中還建立了一個請求器索引器,用於索引RequestManger,後面我們再詳細講。
我們進入最後, 構建Glide。
4. 構建Glide,配置數據轉換器/解碼器/轉碼器/編碼器
回到Glide中,看看Glide的構造函數,這是一個長得變態的構造函數(有200行),但是不必被它嚇倒(好吧,其實第一次看到這裏,我是被嚇倒了,直接略過去了,限於文章篇幅,這裏只截取了部分源碼,仔細的話可以直接看源碼),仔細分析一下,其實整個構造過程並沒那麼複雜。
其中最重要的是步驟3和步驟4,分別爲Glide初始化了模型轉換加載器,解碼器,轉碼器,編碼器,並將對各種類型進行一一註冊,將其列成表格如下:
- 模型轉換器
轉換器 | 功能 |
---|---|
ResourceLoader.StreamFactory | 將Android資源ID轉換爲Uri,在加載成爲InputStream |
ResourceLoader.UriFactory | 將資源ID轉換爲Uri |
ResourceLoader.FileDescriptorFactory | 將資源ID轉化爲ParcelFileDescriptor |
ResourceLoader.AssetFileDescriptorFactory | 將資源ID轉化爲AssetFileDescriptor |
UnitModelLoader.Factory | 不做任何轉換,返回源數據 |
ByteBufferFileLoader.Factory | 將File轉換爲ByteBuffer |
FileLoader.StreamFactory | 將File轉換爲InputStream |
FileLoader.FileDescriptorFactory | 將File轉化爲ParcelFileDescriptor |
DataUrlLoader.StreamFactory | 將Url轉化爲InputStream |
StringLoader.StreamFactory | 將String轉換爲InputStream |
StringLoader.AssetFileDescriptorFactory | 將String轉換爲AssetFileDescriptor |
HttpUriLoader.Factory | 將http/https Uri轉換爲InputStream |
UriLoader.StreamFactory | 將Uri轉換爲InputStream |
UriLoader.FileDescriptorFactory | 將Uri轉換爲ParcelFileDescriptor |
UriLoader.AssetFileDescriptorFactory | 將Uri轉換爲AssetFileDescriptor |
UrlUriLoader.StreamFactory | 將將http/https的Uri轉換爲InputStream |
UrlLoader.StreamFactory | 將Url轉換爲InputStream |
HttpGlideUrlLoader.Factory | 將HttpGlide轉換爲InputStream |
…… | …… |
* 解碼器
解碼器 | 功能 |
---|---|
ByteBufferGifDecoder | 將ByteBuffer解碼爲GifDrawable |
ByteBufferBitmapDecoder | 將ByteBuffer解碼爲Bitmap |
ResourceDrawableDecoder | 將資源Uri解碼爲Drawable |
ResourceBitmapDecoder | 將資源ID解碼爲Bitmap |
BitmapDrawableDecoder | 將數據解碼爲BitmapDrawable |
StreamBitmapDecoder | 將InputStreams解碼爲Bitmap |
StreamGifDecoder | 將InputStream數據轉換爲BtyeBuffer,再解碼爲GifDrawable |
GifFrameResourceDecoder | 解碼gif幀 |
FileDecoder | 包裝File成爲FileResource |
UnitDrawableDecoder | 將Drawable包裝爲DrawableResource |
UnitBitmapDecoder | 包裝Bitmap成爲BitmapResource |
VideoDecoder | 將本地視頻文件解碼爲Bitmap |
* 轉碼器
轉碼器 | 功能 |
---|---|
BitmapDrawableTranscoder | 將Bitmap轉碼爲BitmapDrawable |
BitmapBytesTranscoder | 將Bitmap轉碼爲Byte arrays |
DrawableBytesTranscoder | 將BitmapDrawable轉碼爲Byte arrays |
GifDrawableBytesTranscoder | 將GifDrawable轉碼爲Byte arrays |
* 編碼器
編碼器 | 功能 |
---|---|
ByteBufferEncoder | 將Byte數據緩存爲File |
StreamEncoder | InputStream緩存爲File |
BitmapEncoder | 將Bitmap數據緩存爲File |
BitmapDrawableEncoder | 將BitmapDrawable數據緩存爲File |
GifDrawableEncoder | 將GifDrawable數據緩存爲File |
* 模型轉換註冊表(實在太多,只列出了部分)
源數據 | 轉換數據 | 轉換器 |
---|---|---|
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 |
…… | …… | …… |
以上模型轉換註冊表非常重要,在Glide進入解碼流程時,將會遍歷這裏註冊的所有可能轉換的情形,嘗試進行數據轉換。
這裏只列出部分情形,其它還包括File/Bitmap/Drawable/Byte等等幾乎涵括了日常使用的情況。
Glide的加載流程可以概括爲以下流程:
model(數據源)-->data(轉換數據)-->decode(解碼)-->transformed(縮放)-->transcoded(轉碼)-->encoded(編碼保存到本地)
其中,transformed爲對解碼得到的圖片數據進行縮放,如FitCenter、CropCenter等。
到這裏,Glide單例就構建完成了,讓我們返回到Glide#with中
在構建好Glide後,通過getRequestManagerRetriever()將會得到一個RequestManagerRetriever,即RequestManager的檢索器,RequestManagerRetriever#get()將爲每個請求頁面創建一個RequestManager。
還記得GlideBuilder#build提到的一句代碼嗎?
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);
沒錯,這裏獲取的就是它。這裏就必須要講到Glide數據請求的生命週期了。
我們都知道Glide會根據頁面的生命週期來自動的開啓和結束數據的請求,那麼Glide是怎麼做到的呢?
5. 生命週期管理
我們進入RequestManagerRetriever#get(Activity)方法中。
首先,判斷是否爲後臺線程,如果是,則使用ApplicationContext重新獲取。
重點來看else代碼塊。先斷言請求的頁面是否已經銷燬。否則獲取當前頁面的FragmentManager,並傳給fragmentGet方法。
在fragmentGet中首先通過getRequestManagerFragment()來獲取一個命名爲FRAGMENT_TAG的fragment,如不存在,則新建一個RequestManagerFragment,並添加到當前頁面中。
這裏我們就可以猜到了,Glide是通過在頁面中添加一個Fragment來動態監聽頁面的創建和銷燬,從而達到依賴頁面生命週期,動態管理請求的目的。
在RequestManagerFragment構造函數中,注入了一個生命週期監聽器ActivityFragmentLifecycle,並在Fragment各個生命週期回調中,調用了對應的方法。
而ActivityFragmentLifecycle也緊接着會調用lifecycleListener監聽器,而這個監聽器其實就是RequestManger。如下:
最後,RequestManagerRetriever#fragmentGet,判斷這個Fragment的RequestManager是否存在,否則創建一個RequestManager,並將生命週期注入,同時RquestManager構建時,將會通過addListener注入生命週期回調(具體可以查看RequestManger構造函數)。
最後,Glide#with終將得到一個RequestManager。
至此,Glide的加載過程就解析完畢了。總結一下整個流程:
- 通過AndroidManifest和@GlideModule註解獲取用戶自定義配置GlideModule,並調用其對應的方法
- 通過GlideBuilder構建Glide:
1.新建線程池
2.新建圖片緩存池和緩存池
3.新建內存緩存管理器
4.新建默認本地緩存管理器
5.新建請求引擎Engine
6.新建RequestManger檢索器
7.新建Glide - Glide構造方法中,新建模型轉換器,解碼器,轉碼器,編碼器,以及生成Glide上下文GlideContext
- 通過RequestManager檢索器,建立生命週期監聽,並建立一個RequestManager
- 完成!
三、 Glide與GlideApp
如果在項目中已經使用了Glide3.x,並且想要升級到Glide4.x,那麼你會發現,原來使用鏈式調用進行參數配置的方法已經被修改了,同一個封裝到了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);
這樣的話升級後將導致大量的修改,當然你也可以自己封裝一下,但是Glide已經爲我們做好了兼容方案。
還記得初始化是通過@GlideModule註解來註冊自定義配置嗎?只要在項目中定義這麼一個配置,那麼Glide將會自動幫我們生成一個GlideApp模塊,封裝了Glide3.x中的調用方式。
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
}
調用如下,還是原來的配方,還是熟悉的味道~
GlideApp.with(this)
.load(ImageConfig.URL_WEBP)
.sizeMultiplier(0.5f)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.error(R.mipmap.ic_launcher)
.into(iv);
如果你還覺得不爽,那麼你甚至可以把GlideApp直接修改爲Glide,實現幾乎“無縫對接”。當然,你還是要修改引用路徑的。
@GlideModule(glideName="Glide")
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
}
以上,就是Glide4初始化的源碼解析了,其實還有許多的知識點可以繼續深入,如AndroidManifest解析器,註解的使用(可參考我的另一文Android編譯時註解,和重複代碼Say No!)等等,有興趣的話可以把源碼下下來看看。
Glide4源碼解析系列
[Glide4源碼解析系列]–1.Glide初始化
[Glide4源碼解析系列]–2.Glide數據模型轉換與數據抓取