Android Glide4 源碼解析--框架初始化

原文: 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)用的方法胎挎,即

image

首先沟启,跟進(jìn)getRetriever(activity)

image

這里首先檢查了context是否為空,如果為null犹菇,拋出異常德迹。

我們重點(diǎn)來看Glide.get(context)

image

這里是一個典型的雙檢鎖單例模式。

繼續(xù)跟進(jìn)checkAndInitialzeGlide(context)

image
image

注意這里揭芍,在最后注入了一個GlideBuilder胳搞,這個就是Glide的建造器,用于構(gòu)建Glide的一些參數(shù)和配置。

最后肌毅,來到真正初始化Glide的方法(代碼去除了一些Log打涌曜)。

image

留意最后將初始化得到的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)行初始化的呢索赏?

image

第二行代碼中盼玄,getAnnotationGeneratedGlideModules()會獲取Glide注解自動生產(chǎn)的一個Glide的Module配置器。如下:

image

其中‘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中娜亿。

image

在以上代碼中丽已,發(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ù)雜躲舌,直接看代碼中的注釋丑婿。

image

通過以上一系列工具的新建,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ù)雜。

image

其中最重要的是步驟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中

image

在構(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)方法中闻镶。

image

首先甚脉,判斷是否為后臺線程其爵,如果是八酒,則使用ApplicationContext重新獲取。
重點(diǎn)來看else代碼塊草娜。先斷言請求的頁面是否已經(jīng)銷毀。否則獲取當(dāng)前頁面的FragmentManager猴凹,并傳給fragmentGet方法夷狰。

image

在fragmentGet中首先通過getRequestManagerFragment()來獲取一個命名為FRAGMENT_TAG的fragment,如不存在郊霎,則新建一個RequestManagerFragment沼头,并添加到當(dāng)前頁面中。

這里我們就可以猜到了歹篓,Glide是通過在頁面中添加一個Fragment來動態(tài)監(jiān)聽頁面的創(chuàng)建和銷毀瘫证,從而達(dá)到依賴頁面生命周期,動態(tài)管理請求的目的庄撮。

image

在RequestManagerFragment構(gòu)造函數(shù)中背捌,注入了一個生命周期監(jiān)聽器ActivityFragmentLifecycle,并在Fragment各個生命周期回調(diào)中洞斯,調(diào)用了對應(yīng)的方法毡庆。

而ActivityFragmentLifecycle也緊接著會調(diào)用lifecycleListener監(jiān)聽器,而這個監(jiān)聽器其實(shí)就是RequestManger烙如。如下:

image

最后么抗,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初始化的源碼解析了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末竖般,一起剝皮案震驚了整個濱河市甚垦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涣雕,老刑警劉巖艰亮,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胞谭,居然都是意外死亡垃杖,警方通過查閱死者的電腦和手機(jī)男杈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進(jìn)店門丈屹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伶棒,你說我怎么就攤上這事旺垒。” “怎么了肤无?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵先蒋,是天一觀的道長。 經(jīng)常有香客問我宛渐,道長竞漾,這世上最難降的妖魔是什么眯搭? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮业岁,結(jié)果婚禮上鳞仙,老公的妹妹穿的比我還像新娘。我一直安慰自己笔时,他們只是感情好棍好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著允耿,像睡著了一般借笙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上较锡,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天业稼,我揣著相機(jī)與錄音,去河邊找鬼念链。 笑死盼忌,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掂墓。 我是一名探鬼主播谦纱,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼君编!你這毒婦竟也來了跨嘉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤吃嘿,失蹤者是張志新(化名)和其女友劉穎祠乃,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兑燥,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亮瓷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了降瞳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘱支。...
    茶點(diǎn)故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挣饥,靈堂內(nèi)的尸體忽然破棺而出除师,到底是詐尸還是另有隱情,我是刑警寧澤扔枫,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布汛聚,位于F島的核電站,受9級特大地震影響短荐,放射性物質(zhì)發(fā)生泄漏倚舀。R本人自食惡果不足惜叹哭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望痕貌。 院中可真熱鬧话速,春花似錦、人聲如沸芯侥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柱查。三九已至廓俭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唉工,已是汗流浹背研乒。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淋硝,地道東北人雹熬。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像谣膳,于是被迫代替她去往敵國和親竿报。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容