原文: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2018/0403/9555.html
這里添加了一些自己的閱讀筆記.
版本 4.x
一、前言
在眾多的圖片加載框架中,Glide是Google推薦的,并在自家的項目中大量使用的一個非常強大的框架,專注于平滑滾動,并且還提供Gif,本地Vedio首幀的解碼和顯示。Glide提供了非常便捷的鏈式調用接口,以及豐富的拓展和自定義功能,開發者可以非常簡單地對框架進行配置和圖片再加工。
如今Gilde已經更新到4.x,了解其源碼對更好的使用Glide,以及學習相關的圖片處理技術,學習更優雅的編碼會有很大的幫助。
再github上放了一個最簡單的工程, 可以直接拿來研究代碼
https://github.com/shaopx/GlideApp
當然你要是不嫌麻煩可以去官方地址: 要想正常編譯還是要費點勁的.
https://github.com/bumptech/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中進行指定:
1. `<meta-data android:name="com.test.GlideConfiguration"`
2. `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框架最重要的初始化內容都在其中實現。
1. `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提到的一句代碼嗎?
1. `RequestManagerRetriever requestManagerRetriever =`
2. `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初始化的源碼解析了