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

Glide


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數據模型轉換與數據抓取


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章