探索 Glide 原理

Glide 原理.png

前言

1. Glide 基本用法

接下來的講解將基于 Glide 目前的最新版本 4.11。

Glide 的使用特別簡單,首先添加依賴。

添加依賴png

然后調(diào)用下面這三個方法。

三個方法png
  • with()

    可以傳 Applicaiton、Activity 、FragmentView 等類型的參數(shù),加載圖片的請求會與該參數(shù)的生命周期綁定在一起。

  • load()

    可以傳圖片的網(wǎng)絡(luò)地址、Drawable 等。

  • into()

    一般傳 ImageView 。

2. Glide 基本原理

Glide 圖片加載三階段.png

Glide 圖片加載流程大致三可以分為三個階段:發(fā)起請求、啟動任務(wù)以及解碼圖片。

1. 發(fā)起請求

Glide 加載圖片的第一個階段就是發(fā)起請求,發(fā)起請求最少要調(diào)用 with() 、load()into() 三個方法。

with() 方法要傳一個 Activity、Fragment 、 ViewApplication 參數(shù),這幾種類型的參數(shù)都能夠獲取到能用來綁定生命周期的 Context ,如果 ContextActivity ,那么 Glide 會通過這個 Context 創(chuàng)建一個空白的 Fragment ,這個 Fragment 的意義就是監(jiān)聽 Activity 的生命周期,這樣當 Activity 退出的時候,Glide 會取消這個 Activity 發(fā)起的圖片加載請求,以避免不必要的流量等資源浪費。

發(fā)起請求的第二步是調(diào)用 RequestManagerload() 方法,load() 方法做的事情比較少,只是把入?yún)⒆鳛?model 變量的值,這個 model 是用來創(chuàng)建 Request 的。

發(fā)起請求的第三步是調(diào)用 RequestBuilderinto() 方法,并傳入一個 ImageView ,into() 方法中會創(chuàng)建一個請求 Request 對象,然后會通過請求跟蹤器 RequestTracker 調(diào)用 Requestbegin() 方法。

2. 啟動任務(wù)

Glide 加載圖片的第二個階段就是啟動任務(wù),這個任務(wù)就是解碼任務(wù) DecodeJob。

當 Request 的 begin() 方法被 RequestTracker 調(diào)用后,Request 會調(diào)用 TargetgetSize() 方法獲取最終的圖片大小,Target 接口有很多實現(xiàn)類,比如我們傳到 into() 中是 ImageView 的話,那 Target 就是 ImageViewTarget

獲取到 Target 的大小后,Request 就會調(diào)用加載引擎 Engine 的 load() 方法嘗試從內(nèi)存中獲取已有圖片,如果獲取不到的話,就會啟動 DecodeJob 開始從圖片來源加載請求。

3. 解碼圖片

Glide 圖片加載的第三個階段就是解碼圖片,這個階段是從 DecodeJobrun() 方法開始的。

當 DecodeJob 的 run() 方法被 Engine 調(diào)用后,DecodeJob 就會調(diào)用 ModelLoader 的 buildLoadData() 方法從圖片來源 model 獲取圖片,獲取到的圖片原始數(shù)據(jù)就是 Data ,一般 Data 就是輸入流 InputStream 。

然后 ModelLoader 會調(diào)用 ResourceDecoder 的 decode() 方法把 Data 解碼為 Resource ,Resource 就是 Glide 根據(jù) Target 的大小對 Data 進行縮放后的圖片資源。

把 Data 轉(zhuǎn)換為 Resource 后, DecodeJob 會調(diào)用 Transformation 的 transform() 方法應(yīng)用變換選項,這時 Resource 變成了 TransformedResource 。

然后 DecodeJob 會調(diào)用 ResourceTranscoder 的 transcode() 方法對資源進行轉(zhuǎn)碼操作,轉(zhuǎn)碼后的資源就是 TranscodedResource。

轉(zhuǎn)碼操作完成后,Engine 就會調(diào)用 Request 的 onResourceReady() 方法, 然后 Request 會調(diào)用 Target 的 onResourceReady() 方法,如果是 ImageViewTarget 的話,那 onResourceReady() 方法就會把圖片設(shè)置到 ImageView 中。

當資源傳給 Target 處理后,DecodeJob 就會調(diào)用 ResourceEncoder 的 encode() 的方法,把圖片資源編碼到磁盤中。

4. Glide 緩存原理

[圖片上傳失敗...(image-392d44-1623041354520)]

Glide 的緩存用的是三級緩存機制,圖片的緩存分為內(nèi)存緩存、磁盤緩存和來源緩存,也就是從內(nèi)存中獲取不到圖片時,再到磁盤獲取,還是獲取不到就去圖片來源獲取,比如從服務(wù)器上獲取。

對于內(nèi)存緩存,Glide 把內(nèi)存緩存分為了 ActiveResources 和 MemoryCache 兩級,如果從 ActiveResources 拿不到資源,則從 MemoryCache 中獲取資源,其中 MemoryCache 的具體實現(xiàn)是 Glide 中的 LruCache 。

在 ActiveResources 中有類型為 Map 的 activeEngineResources 的字段和一個類型為 ReferenceQueue 的 resourceReferenceQueue 字段,ResourceWeakReference 就是圖片資源的弱引用,父類是 WeakReference ,而 ReferenceQueue 就是引用隊列,垃圾回收器在檢測到恰當?shù)目蛇_性變化后,會把已注冊的引用對象添加到引用隊列中。

然后 MemoryCache 是一個接口,這個接口的實現(xiàn)類是 LruResourceCache ,這個類的主要邏輯在 Glide 自己實現(xiàn)的一個 LruCache 類中。

而 Glide 的磁盤緩存主要是由 DiskLruCache 和 ResourceEncoder 合作完成的,LruCache 是內(nèi)存中的圖片資源的緩存實現(xiàn), DiskLruCache 則是磁盤中的圖片文件的緩存實現(xiàn),而 ResourceEncoder 則負責(zé)做具體的寫入文件操作。

之所以把第三級緩存叫來源緩存,是因為嚴格意義上,就算圖片文件不是在服務(wù)器上而是在設(shè)備上,只要圖片對應(yīng)的文件目錄不屬于 Glide 管理的范圍,那么也是外部來源,Glide 中定義了 5 種數(shù)據(jù)源,分別是 LOCAL、REMOTE、DATA_DISK_CACHE、RESOURCE_DISK_CACHE 和 MEMORY_CACHE ,其中 LOCAL 和 REMOTE 就是外部來源,REMOTE 數(shù)據(jù)源一般就是由 HttpGlideUrlLoader 來加載,而 LOCAL 數(shù)據(jù)源則是由 UriLoader 和 MediaStoreFileLoader 等 ModelLoader 來加載。

5. Glide 初始化流程與配置

這一節(jié)會講解 Glide 的初始化流程,包括 Glide 調(diào)用配置的方式、AppGlideModule 的兩個方法中用到的 Registry 和 GlideBuilder 在 Glide 中的作用。

6. Glide 圖片加載選項

Glide 開放了非常多的圖片加載選項,我們不一定全都用得上,但是了解這些選項,可以讓我們在需要的時候能調(diào)用對應(yīng)的選項,不用再自己實現(xiàn)一遍。

1. 發(fā)起請求

當我們調(diào)用 with() 方法時,Glide 會通過 RequestManagerRetriver 找出或創(chuàng)建一個當前 Activity 對應(yīng)的 RequestManager ,一個 Context 對應(yīng)一個 RequestManager,RequestManager 有 3 個作用:綁定生命周期、監(jiān)聽網(wǎng)絡(luò)狀態(tài)、創(chuàng)建請求構(gòu)建器。

1. 綁定生命周期

當我們調(diào)用 with() 方法時,RequestManager 會用對應(yīng)的 Context 創(chuàng)建一個 RequestManagerFragment ,RequestManagerFragment 是一個無布局的 Fragment。

RequestManagerFragment 主要是用來做生命周期關(guān)聯(lián)的,當這個 Fragment 感知到 Activity 的生命周期發(fā)生變化時,就會告訴 RequestManager,讓它去做暫停請求、繼續(xù)請求和取消請求等操作。

如果我們用的是 ApplicationContext 加載某張圖片,那就意味著這次圖片加載操作的生命周期是與應(yīng)用的生命周期綁定的。

2. 監(jiān)聽網(wǎng)絡(luò)狀態(tài)

RequestManager 中有一個網(wǎng)絡(luò)連接監(jiān)聽器 RequestManagerConnectivityListener ,它實現(xiàn)了ConnectivityListener 接口,每次網(wǎng)絡(luò)狀態(tài)切換時,RequestManager 就會重啟所有的圖片加載請求。

3. 創(chuàng)建請求構(gòu)建器

我們在加載圖片時調(diào)用的 load() 方法是 RequestManager 的方法,調(diào)用這個方法其實是創(chuàng)建了一個請求構(gòu)建器 RequestBuilder,RequestManager 中有很多創(chuàng)建 RequestBuilder 的方法,比如 asDrawable()、asBitmap() 、asFile() 等,這些方法對應(yīng)著不同泛型參數(shù)的 RequestBuilder 。

2. 啟動任務(wù)

image

在我們調(diào)用 into() 方法后,RequestBuilder 會通過 RequestTracker 調(diào)用 Request 的 begin() 方法,然后 Request 在獲取到 Target 的大小后就會調(diào)用 Engine 的 load() 方法,然后 Engine 會嘗試從內(nèi)存中獲取圖片,獲取不到的話則啟動 DecodeJob 。

2.1 Request

對于我們發(fā)起的圖片加載請求,Glide 會把這個請求封裝為 Request,而 RequestBuilder 就是 Request 的構(gòu)建器,當我們調(diào)用 RequestBuilder 的 into() 方法時,RequestBuilder 會創(chuàng)建一個 Request ,并把 Request 傳給請求管理器。

RequestBuilder 繼承了 BaseRequestOptions 抽象類,我們平時用的 centerCrop() 等方法大部分都是 BaseRequestOptions 的方法,關(guān)于圖片加載選項在后面會講到。

2.1.1 Request 的 6 種狀態(tài)

前面講到的 Request 具體就是 SingleRequest ,SingleRequest 中有一個 Status 枚舉類,包含了請求的 6 種狀態(tài)。

1. 待運行 PENDING

當我們通過 into() 創(chuàng)建了一個 SingleRequest 后,該 Request 就進入了待運行狀態(tài)。

2. 已清除 CLEARED

每次我們用 into() 方法加載圖片時,RequestManager 都會先看下我們傳入的 Target 是否有對應(yīng)的 Request ,如果有的話就會調(diào)用該 Request 的 clear() 方法釋放資源,這時 Request 就進入了已清除狀態(tài)。

3. 待測量 WAITING_FOR_SIZE

當 RequestManager 調(diào)用 RequestTracker 的 runRequest() 方法后,RequestTracker 就會調(diào)用 Request 的 begin() 方法,這時請求就進入了待測量狀態(tài)。

4. 運行中 RUNNING

在 SingleRequest 的 begin() 方法中,調(diào)用了 Target 的 getSize() 方法獲取 ImageView 的尺寸,獲取到尺寸后,SingleRequst 會調(diào)用 Engine 的 load() 方法啟動圖片加載請求,這時 Request 就進入了運行中狀態(tài)。

5. 已完成 COMPLETE

當 Engine 從內(nèi)存中加載到資源,或者通過解碼任務(wù)加載到資源后,就會調(diào)用 SingleRequest 的 onResourceReady() 方法,這時 Request 就進入了已完成狀態(tài)。

6. 失敗 FAILED

當解碼任務(wù) DecodeJob 在處理圖片的過程中遇到異常時,就會調(diào)用 EngineJob 的 onLoadFailed() 方法,然后 EngineJob 會調(diào)用 SingleRequest 的 onLoadFailed() 方法,這時 SingleRequest 就進入了失敗狀態(tài)。

2.1.4 三種占位圖

我們在加載圖片時,可以設(shè)置 placeholder、error 和 fallback 三種占位圖。

  • placeholder

    圖片加載完成前顯示的占位圖;

  • error

    圖片加載失敗時顯示的占位圖;

  • fallback

    圖片來源為空時顯示的占位圖;

使用占位圖時,要注意占位圖是不會使用 Transformation 進行變換的,如果你想弄個圓角或圓形的占位圖,可以用 submit().get() 獲取對應(yīng)變換后的占位圖的 Drawable 對象,然后傳到對應(yīng)的占位圖設(shè)置方法中。

2.2 Target

image

當我們調(diào)用 into() 方法,傳入 ImageView 后,Glide 會把 ImageView 轉(zhuǎn)化為 Target ,下面我們來看下不同 Target 的作用。

2.2.1 ImageViewTarget

1. SizeDeterminer

ImageViewTarget 繼承了 ViewTarget ,在 ViewTarget 中有一個用來獲取尺寸的 SizeDeterminer ,SizeDeterminer 的 getSize() 方法拿到的尺寸,是把 ImageView 的內(nèi)邊距 padding() 去掉后的尺寸。

在 Glide 中,寬高分為請求寬高和原始寬高 ,而 SizeDeterminer 拿到的尺寸就是請求寬高,Glide 會根據(jù)請求寬高對圖片進行縮放操作,以減少不必要的內(nèi)存消耗。

2. OnPreDrawListener

當 Request 獲取 View 的尺寸失敗時,ViewTarget 會通過 ViewTreeObserver 的 OnPreDrawListener 的回調(diào)來獲取 View 的尺寸,然后再傳給 Request。

3. setResource()

ImageViewTarget 主要有 BitmapImageViewTarget 和 DrawableImageViewTarget 兩個子類,它們兩個的區(qū)別就在于它們的 setResource() 方法。

  • BitmapImageViewTarget

    setResource() 用的是 ImageView 的 setImageBitmap() 方法;

  • DrawableImageViewTarget

    setResource() 用的是 ImageView 的 setImageDrawable() 方法;

2.2.2 RequestFutureTarget

1. submit()

FutureTarget 是一個實現(xiàn)了 Future 和 Target 接口的接口,它只有一個 RequestFutureTarget 子類 ,當我們用 submit() 方法獲取 Glide 加載好的圖片資源時,就是創(chuàng)建了一個 RequestFutureTarget 。

2. Waiter

RequestFutureTarget 是用 wait/notify 的方式來實現(xiàn)等待和通知的,這兩個是 Object 的方法,Request 中有一個 Waiter ,當 DecodeJob 加載到圖片后,RequestFutureTarget 就會讓 Waiter 發(fā)出通知,這時我們的 get() 方法就能獲取到返回值了。

這就是為什么我們用 RequestFutureTarget 的 get() 方法獲取圖片時,要把這個操作放在子線程運行。

2.2.3 CustomTarget

給不是 View 的 Target 加載圖片時,Glide 都把它作為 CustomTarget 。

1. PreloadTarget

預(yù)加載 Target 。

當我們調(diào)用 preload() 選項預(yù)加載圖片時,Glide 會把圖片交給 PreloadTarget 處理,當 PreloadTarget 接收到圖片資源后,就會讓 RequestManager 把該請求的資源釋放掉。

因為不需要等待資源加載完成,所以我們在用 preload() 預(yù)加載圖片時,不用像 submit() 一樣在子線程中執(zhí)行。

2. AppWidgetTarget

桌面組件 Target 。

當 AppWidgetTarget 接收到處理好的圖片資源后,會把它設(shè)置給 RemoteView ,然后通過桌面組件管理器 AppWidgetManager 更新桌面組件。

3. DelayTarget

GifTarget。

這是加載 Gif 圖片時要用到的 Target ,關(guān)于 Glide 加載 Gif 圖片的流程在后面會講到。

4. NotificationTarget

通知欄 Target 。

這個 Target 有一個 setBitmap 方法,會把圖片設(shè)置給通知欄的 RemoteView ,然后通過 NotificationManager 更新通知欄中的通知。

2.3 Engine

下面我們來看一些與 Engine 相關(guān)的實現(xiàn)。

  • Engine 的作用
  • Key 的作用
  • Resource 的作用
  • BitmapPool

2.3.1 Engine 的作用

Engine 是 Glide 的圖片加載引擎,是 Glide 中非常重要的一個類,下面我們來看下 Engine 的作用。

1. load()

[圖片上傳失敗...(image-78818c-1623041354519)]

前面講到了當我們調(diào)用 into() 方法時,就是間接調(diào)用了 Request.begin() 方法,而 Request 的 begin() 方法又調(diào)用了 Engine 的 load() 方法。

在 load() 方法中,Engine 會先用 EngineKeyFactory 創(chuàng)建資源標識符 Key,然后用這個 Key 去內(nèi)存緩存中加載資源。

如果從內(nèi)存中找到了資源,Engine 就會直接把資源回傳給 Resource,如果沒有加載到資源,Engine 就會創(chuàng)建并啟動新的 EngineJob 和解碼任務(wù) DecodeJob。

2. EngineKeyFactory

EngineKeyFactory 是 Engine 中一個負責(zé)生產(chǎn) EngineKey 的工廠,EngineKey 是引擎任務(wù)資源標識符,關(guān)于什么是 Key 后面進一步講。

在 Engine 啟動新的任務(wù)加載圖片前,會先通過 EngineKeyFactory 創(chuàng)建一個 EngineKey,然后讓 DecodeJob 把資源與 EngineKey 進行綁定,這里說的綁定,其實就是把 model 放到 EngineKey 中。

3. 回收資源

Engine 中有一個資源回收器 ResourceRecycler ,Resource 接口中有一個 recycle() 方法,關(guān)于 Resource 我們后面再講。

這里只要知道,當 SingleRequest 被清除,比如在 into() 方法中發(fā)現(xiàn) Target 已經(jīng)有對應(yīng)的 Request 時,Request 就會讓 Engine 釋放資源,具體做釋放資源操作的就是 ResourceRecycler。

4. 磁盤緩存提供器

LazyDiskCacheProvider 是 Engine 中的一個靜態(tài)內(nèi)部類,是磁盤緩存 DiskCache 的提供器,DiskCache 是一個接口,關(guān)于 DiskCache 的實現(xiàn)我們后面再講。

5. 啟動新的解碼任務(wù)

當 Engine 從內(nèi)存中找不到對應(yīng)的 Key 的資源時,就會啟動新的解碼任務(wù)。

Engine 會用加載任務(wù)工廠 EngineJobFactory 構(gòu)建一個加載任務(wù) EngineJob,然后再構(gòu)建一個解碼任務(wù) DecodeJob。

EngineJob 這個名字看起來很霸氣,但是實際上它并沒有做什么事情,它只是 Engine 與 DecodeJob 之間溝通的橋梁。

當構(gòu)建了 EngineJob 和 DecodeJob 后,Engine 就會把 DecodeJob 提交到線程池 GlideExecutor 中。

2.3.2 Key

前面講到了 Engine 會通過 EngineKeyFactory 創(chuàng)建資源標識符 Key ,那什么是 Key ?

Key 是 Glide 中的一個接口,是圖片資源的標識符。

1. 避免比較有誤

Glide 的內(nèi)存緩存和磁盤緩存用的都是 Glide 自己實現(xiàn)的 LruCache,LruCache 也就是最近最少使用緩存算法(Least Recently Used),LruCache 中有一個 LinkedHashMap ,這個 HashMap 的 Key 就是 Key 接口,而 Value 則是 Resource 接口。

在用對象作為 HashMap 的 Key 時,要重寫 equals() 和 hashCode() 方法。

如果不重寫這兩個方法,那么當兩個 Key 的內(nèi)存地址不同,但是實際代表的資源相同時,使用父類 Object的 hasCode() 直接用內(nèi)存地址做比較,那么結(jié)果會是不相等。

此外 Object 的 equals() 方法也是拿內(nèi)存地址作比較,所以也要重寫。

比如下面就是 ResourceCacheKey 的 equals() 判斷邏輯。

image
2. Key 實現(xiàn)類
image

下面是幾個實現(xiàn)了 Key 接口的類。

  • DataCacheKey

    原始圖片數(shù)據(jù)標識符。

  • ResourceCacheKey

    處理后的圖片資源標識符。

  • AndroidResourceSignature

    Android 資源標識符。當我們傳入 into() 方法的圖片是 R.drawable.xxx 時,Glide 就會把它封裝為 AndroidResourceSignature 。

  • ObjectKey

    通用資源標識符。

    可以說除了 App 自帶的 Android 資源以外的圖片資源都會用 ObjectKey 作為標識符,比如本地圖片文件。

  • EngineKey

    引擎資源標識符。

    這個 Key 是 Engine 對其他 Key 的封裝,這時傳進來的 Key 是以簽名(Signature)的身份存在 EngineKey 中的。

2.3.3 Resource

前面講到了 Engine 會通過 ResourceRecycler 來回收資源,而 ResourceRecycler 調(diào)用了 Resource 的 recycle() 方法。

可能你想起來 Bitmap 就有一個可以回收圖片內(nèi)存的 recycle() 方法,沒錯,Glide 回收 Bitmap 的方式就是用的 Bitmap 自帶的 recycle() 方法,但是這個過程又比這復(fù)雜一些。

Resource 是一個接口,其中一個實現(xiàn)類是 BitmapResource ,也就是位圖資源,比如網(wǎng)絡(luò)圖片就會轉(zhuǎn)化為 BitmapResource。

在 BitmapResource 中有一個位圖池 BitmapPool,這是 Glide 用來復(fù)用 Bitmap 的一個接口,具體的實現(xiàn)類是 LruBitmapPool 。

在 BitmapResource 的 recycle() 方法中,會把對應(yīng)的 Bitmap 通過 put() 方法放到 BitmapPool 中,關(guān)于 BitmapPool 在講 Glide 緩存原理時會進一步講。

2.4 DecodeJob

前面講到了 Engine 在緩存中找不到資源時,就會創(chuàng)建新的加載任務(wù) EngineJob 和新的解碼任務(wù) DecodeJob ,然后讓 EngineJob 啟動 DecodeJob。

DecodeJob 實現(xiàn)了 Runnable 接口,EngineJob 啟動 DecodeJob 的方式就是把它提交給 GlideExecutor,如果我們沒有調(diào)整磁盤緩存策略的話,那默認用的就是 diskCacheExecutor ,關(guān)于 GlideExecutor 在第 4 大節(jié)會講,下面我們先看下 DecodeJob 的實現(xiàn)。

2.4.1 runWrapped()

DecodeJob 的 run() 方法只是對 runWrapped() 可能遇到的異常進行了捕獲,而 runWrapped() 方法會根據(jù)不同的運行理由 RunReason 運行不同的數(shù)據(jù)生成器。

image
1. 三種運行理由

runWrapped() 會根據(jù)下面三種運行理由來執(zhí)行解碼任務(wù)。

  • INITAILIZE

    從緩存中獲取數(shù)據(jù)并解碼;

  • SWITCH_TO_SOURCE_SERVICE

    從來源獲取數(shù)據(jù)后再進行解碼;

  • DECODE_DATA

    當獲取數(shù)據(jù)的線程與 DecodeJob 的線程不同時,比如使用了 OkHttp-Integration 時,DecodeJob 會直接對數(shù)據(jù)進行解碼;

2. 初始化

當運行理由為默認狀態(tài) INITIALIZE 時,DecodeJob 會從磁盤中獲取圖片數(shù)據(jù)并進行解碼。

3. 從來源獲取數(shù)據(jù)

當 DecodeJob 從緩存中獲取不到數(shù)據(jù)時,就會把運行理由改為 SWITCH_TO_SOURCE_SERVICE ,也就是從來源獲取數(shù)據(jù),然后運行來源數(shù)據(jù)生成器 SourceGenerator 。

4. 對檢索到的數(shù)據(jù)進行解碼

DecodeJob 通過數(shù)據(jù)生成器獲取到數(shù)據(jù)后,就會調(diào)用 decodeFromRetrievedData() 方法來對檢索到的數(shù)據(jù)進行解碼。

2.4.2 DecodeJob 數(shù)據(jù)獲取流程

image

在 DecodeJob 的 getNextStage() 方法中,會根據(jù)當前的解碼步驟 stage 來判斷進行什么操作。

DecodeJob 把提取數(shù)據(jù)分為了 6 個階段,這 6 個階段是 Stage 枚舉類中的值。

1. INITIALIZE

初始化。

當解碼處于這個階段時,DecodeJob 會根據(jù)磁盤緩存策略,判斷是否要從磁盤緩存中獲取處理過的圖片資源,是的話就用 ResourceCacheGenerator 獲取圖片資源,當用 ResourceCacheGenerator 獲取到 Resource 后,就會開始對資源進行解碼。

如果磁盤緩存策略設(shè)定了不從緩存中獲取 Resource,那就會切換到 RESOURCE_CACHE 階段。

2. RESOURCE_CACHE

從緩存中獲取處理過的圖片資源。

當解碼處于這個階段時,DecodeJob 會根據(jù)磁盤緩存策略,判斷是否要從磁盤緩存中獲取未處理過的圖片原始數(shù)據(jù),是的話就用 DataCacheGenerator 獲取圖片數(shù)據(jù)。

3. DATA_CACHE

從緩存中獲取原始數(shù)據(jù)。

如果磁盤緩存策略設(shè)定了不獲取緩存中的圖片資源和原始數(shù)據(jù) ,又或者是獲取不到數(shù)據(jù),DecodeJob 那就會切換到 DATA_CACHE 階段。

如果我們在加載圖片時調(diào)用了 onlyRetrieveFromCache(true) ,那么 DecodeJob 就會不會切換到 SOURCE 階段從來源獲取數(shù)據(jù),而是會切換到 FINISH 階段結(jié)束數(shù)據(jù)獲取流程。

否則就會切換到 SOURCE 階段。

4. SOURCE

從圖片來源獲取原始數(shù)據(jù)。

如果 DecodeJob 在 RESOURCE_CACHE 和 DATA_CACHE 階段都沒有拿到圖片數(shù)據(jù),那就會用 SourceGenerator 從圖片來源獲取圖片數(shù)據(jù)。

5. ENCODE

編碼。

當磁盤緩存策略設(shè)定了要對圖片資源進行緩存時,那么在獲取到數(shù)據(jù)后,DecodeJob 就會用 ResourceDecoder 對資源進行編碼,也就是把圖片放到磁盤緩存中。

6. FINISH

結(jié)束。

2.4.3 三種數(shù)據(jù)生成器

當 DecodeJob 切換階段后,會調(diào)用 getNextGenerator() 切換不同階段對應(yīng)的生成器,這里說的生成器,指的是 DataFetcherGenerator 接口。

DataFetcherGenerator 不是像名字說的那樣用來創(chuàng)建 DataFetcher 的,DataFetcherGenerator 與 DataFetcher 是通過 ModelLoader 來關(guān)聯(lián)的。

DataFetcherGenerator 會通過 ModelLoader 構(gòu)建數(shù)據(jù)封裝對象 LoadData ,然后通過 LoadData 中的 DataFetcher 來加載數(shù)據(jù)。

LoadData 是 ModelLoader 的內(nèi)部類,它有來源標識符 Key 和 DataFetcher 兩個字段。

在 ModelLoader 中最重要的就是 buildLoadData() 方法,不同類型的 Model 對應(yīng)的 ModelLoader 所創(chuàng)建出來的 LoadData() 也不同。

下面我們來看下 DataFetcherGenerator ,這個接口中最重要的方法是 startNext() ,具體實現(xiàn)了這個接口有下面三個類。

  • SourceGenerator

    來源數(shù)據(jù)生成器。

  • DataCacheGenerator

    原始緩存數(shù)據(jù)生成器。

  • ResourceCacheGenerator

    緩存資源生成器。

以 SourceGenerator 為例,我們來看下 startNext() 方法的處理流程。

image
1. 是否獲取到了需要緩存的數(shù)據(jù)

當 SourceGenerator 加載完數(shù)據(jù)后,會再次進入 startNext() 方法,這時就獲取到了需要緩存的數(shù)據(jù)。

2. 是否保存原始數(shù)據(jù)

如果磁盤緩存策略設(shè)定了要保存圖片的原始數(shù)據(jù),就用數(shù)據(jù)提取器加載數(shù)據(jù),否則就直接把圖片加載給 Target 。

3. 加載數(shù)據(jù)

當需要保存原始數(shù)據(jù)或數(shù)據(jù)有加載路徑時,SourceGenerator 就會根據(jù) Model 的類型,使用對應(yīng)的 DataFetcher 來提取數(shù)據(jù),比如從網(wǎng)絡(luò)上下載圖片。

4. 是否保存原始數(shù)據(jù)

當 SourceGenerator 獲取到數(shù)據(jù)后,會再次判斷是否要保存原始數(shù)據(jù),否則就直接把圖片加載給 Target 。

5. 編碼

當 SourceGenerator 從 DataFetcher 中拿到數(shù)據(jù)后,會再走一遍 startNext() 方法,然后用編碼器 Encoder 對數(shù)據(jù)進行編碼,也就是把圖片放到磁盤緩存中。

6. 從磁盤中獲取數(shù)據(jù)

當 SourceGenerator 把數(shù)據(jù)保存到磁盤后,不會直接加載圖片,而是從磁盤中拿這張圖片,然后再進行加載。

2.4.4 onResourceDecoded()

image

當 DecodeJob 調(diào)用 ResourceDecoder 的 decode() 方法,并且獲取到編碼結(jié)果后,會調(diào)用 onResourceDecoded() 方法應(yīng)用變換選項以及初始化編碼管理器。

1. 應(yīng)用變換選項

對于處理過的 Resource,onResourceDecoded() 不會再次進行變換,否則就會對圖片進行變換操作。

2. 回收圖片資源

當對資源應(yīng)用了變換選項后,DecodeJob 會把原來的資源回收掉,因為這個資源接下來也用不上了。

3. 緩存變換后圖片資源

onResourceDecoded() 方法中,會根據(jù)磁盤緩存策略判斷是否要對資源進行編碼,如果要進行編碼的話,會根據(jù)不同的編碼策略創(chuàng)建不同的 Key 。

Glide 有 SOURCE 和 TRANSFORMED 兩種編碼策略,分別代表對原始數(shù)據(jù)進行編碼和對變換后資源進行編碼。

  • SOURCE

    GIF 編碼器 GifDrawableEncoder 中用的編碼策略;

  • TRANSFORMED

    位圖編碼器 BitmapEncoder 中用的編碼策略;

4. 初始化編碼管理器

創(chuàng)建好 Key 后不會直接對圖片進行編碼,而是會修改編碼管理器的 Key ,等到轉(zhuǎn)碼完成后再用 ResourceEncoder 進行編碼。

3. 解碼圖片

[圖片上傳失敗...(image-59f320-1623041354519)]

看完了解碼任務(wù)啟動流程,下面我們來看下當 DecodeJob 獲取到圖片數(shù)據(jù)后是怎么處理這些數(shù)據(jù)的。

其中關(guān)于 Target 在 1.2 小節(jié)已經(jīng)講過,下面就不再多講了,我們來看下其他的對象。

Glide 對圖片解碼的過程涉及下面 6 個概念.

Glide 會以 Model 的形式封裝圖片來源 ,Model 可以是 URL、本地文件和網(wǎng)絡(luò)圖片等類型。

Glide 把數(shù)據(jù)源轉(zhuǎn)換為Model 后,會把它加工成原始數(shù)據(jù) Data ,一般就是輸入流 InputStream ,而 ModelLoader 則負責(zé)從 Data 獲取原始數(shù)據(jù)。

Glide 獲取到原始數(shù)據(jù)后,會用資源解碼器 ResourceDecoder 把原始數(shù)據(jù) Data 解碼為Resource,比如把輸入流 InputStream 解碼為 Bitmap。

Glide 會根據(jù)我們調(diào)用的變換選項用 Transformation 處理 Resource ,比如用 centerCrop() 裁剪就是一種變換選項,變換后的 Resource 就叫 TransformedResource ,負責(zé)變換的就是 Transformation 。

為了統(tǒng)一處理靜態(tài)和動態(tài)圖片,Glide 會用 ResourceTranscoder 把 Bitmap 轉(zhuǎn)碼為 GlideBitmapDrawable 。

最終會把圖片顯示到目標 Target 上,比如 ImageView 對應(yīng)的就是 ImageViewTarget 。

3.1 ModelLoader

ModelLoader 是一個接口,負責(zé)創(chuàng)建 LoadData ,它有兩個泛型參數(shù) Model 和 Data。

  • Model

    代表圖片來源的類型,比如圖片的網(wǎng)絡(luò)地址的 Model 類型為 String ;

  • Data

    代表圖片的原始數(shù)據(jù)的類型,比如網(wǎng)絡(luò)圖片對應(yīng)的類型為 InputStream ;

1. Factory

在 DataFetcherGenerator 獲取圖片數(shù)據(jù)時,會調(diào)用 ModelLoaderRegistry 的 getModelLoaders() 方法,這個方法中會根據(jù) model 的類型用 MultiModelLoaderFactory 生成對應(yīng)的 ModelLoader,比如能夠解析字符串的 ModelLoader 就有 7 個,關(guān)于 ModelLoaderRegistry 在后面講 Glide 配置的時候會講到。

此外每一個 ModelLoader 的實現(xiàn)類中都定義了一個實現(xiàn)了 ModelLoaderFactory 接口的靜態(tài)內(nèi)部類 。

2. handles()

一個 Model 對應(yīng)這么多 ModelLoader,每個 ModelLoader 加載數(shù)據(jù)的方式都不同,這時候就要用 handles() 方法了。

ModelLoader 接口有 handles() 和 buildLoadData() 兩個方法,handles() 用于判斷某個 Model 是否能被自己處理,比如 HttpUriLoader 的 handles() 會判斷傳進來的字符串是否以 http 或 https 開頭,是的話則可以處理。

3. buildLoadData()

ModelLoader 之間是存在嵌套關(guān)系的,比如 HttpUriLoader 的 buildLoadData() 方法就是調(diào)用的 HttpGlideUrlLoader 的 buildLoadData() 方法,HttpGlideUrlLoader 會創(chuàng)建一個 HttpUrlFetcher ,然后把它放到 LoadData() 中。

LoadData 是 ModelLoader 中定義的一個類,它只是放置了圖片來源的 Key 和要用來提取數(shù)據(jù)的 DataFetcher ,沒有其他方法。

3.2 ResourceDecoder

DataFetcherGenerator 使用 ModelLoader 構(gòu)建完數(shù)據(jù)后,就會用 DataRewinder 對數(shù)據(jù)進行重繞,也就是重置數(shù)據(jù),比如 InputStreamRewinder 就會調(diào)用 RecyclableBufferedInputStream 的 reset() 方法重置輸入流對應(yīng)的字節(jié)數(shù)組的位置。

ResourceDecoder 是一個接口,有非常多的實現(xiàn)類,比如網(wǎng)絡(luò)圖片對應(yīng)的解碼器為 StreamBitmapDecoder ,StreamBitmapDecoder 的 decode() 方法調(diào)用了降采樣器 Downsampler 的 decode() 方法,下圖是 Downsampler 的解碼邏輯。

image
1. 設(shè)置目標寬高

除非我們通過 override() 方法把尺寸改為 Target.SIZE_ROGINAL ,否則 Glide 默認會把 ImageView 的大小作為加載圖片的目標寬高。

2. 計算縮放后寬高

根據(jù)不同的變換選項計算縮放后寬高。

3. 創(chuàng)建空 Bitmap

根據(jù)計算后的目標寬高創(chuàng)建一個空的 Bitmap 。

4. 使用 BitmapFactory 解碼
image

Downsampler 的解碼方式用的是 ImageReader 的 decodeBitmap() 方法,而 ImageReader 又調(diào)用了 BitmapFactory 的 decodeStream() 方法,BitmapFactory 最終調(diào)用的是 SkImageDecoder 的 decode() 方法。

5. 把 Bitmap 放入 BitmapPool 中

在前面講 Resource 的時候講到了 BitmapResource 中有一個 BitmapPool,這個 BitmapPool 是由 Downsampler 傳過去的,而 Downsampler 的 BitmapPool 是由 Glide 創(chuàng)建并傳進來的。

3.3 Transformation

Transformation 是一個接口,它有一個 transform() 方法,這個方法是在 DecodeJob 中調(diào)用的,當 DecodeJob 發(fā)現(xiàn)數(shù)據(jù)源不是緩存中的 Resource 時,就會調(diào)用變換選項的 transform() 方法。

Transformation 的其中一個實現(xiàn)類是 BitmapTransformation,我們平時調(diào)用的 centerCrop() 就是 BitmapTransformation 的子類,centerCrop() 選項對應(yīng)的是 CenterCrop 類,它實現(xiàn)了 Transformation 接口,具體的變換實現(xiàn)在 TransformationUtils 中。

1. Matrix

以 centerCrop() 為例,TransformationUtils 的 centerCrop() 方法會先創(chuàng)建一個 Matrix 矩陣,然后根據(jù)傳進來的 Bitmap 計算 Matrix 的縮放比例和平移坐標。

2. drawBitmap()

配置好 Matrix 后,就會根據(jù)目標寬高創(chuàng)建一個空的目標 Bitmap ,然后把原始 Bitmap、目標 Bitmap 和 Matrix 傳給 Canvas 的 drawBitmap() 方法,然后返回 Canvas 處理好的圖片。

3.4 ResouceTranscoder

ResourceTranscoder 是一個接口,是 Glide 中的資源轉(zhuǎn)碼器,它有兩個泛型參數(shù) Z 和 R ,分別代表需要進行原始類型和轉(zhuǎn)碼目標類型。

比如 BitmapDrawableTranscoder 的原始類型是 Bitmap,轉(zhuǎn)碼目標類型是 BitmapDrawable,在BitmapDrawableTranscoder 的 transcode() 方法中,會把 Bitmap 轉(zhuǎn)換為 BitmapDrawable ,以便 Target 進行處理。

3.5 ResourceEncoder

ResourceEncoder 是一個接口,是 Glide 中的資源編碼器,ResourceEncoder 有好幾個實現(xiàn)類,比如網(wǎng)絡(luò)圖片對應(yīng)的編碼器為 StreamEncoder。

在轉(zhuǎn)碼完成后,DecodeJob 會先把圖片加載到 Target 中,然后用 ResourceEncoder 對圖片進行編碼,比如 StreamEncoder 的編碼操作就是把輸入流 InputStream 轉(zhuǎn)化為圖片文件,然后保存到本地。

4. Glide 緩存原理

Glide 使用了三級緩存機制,圖片的緩存分為內(nèi)存、磁盤和來源,也就是從內(nèi)存獲取不到圖片時,再去磁盤獲取圖片,從磁盤獲取不到圖片時,再從圖片來源獲取圖片。

三級緩存的優(yōu)勢在于節(jié)省流量和內(nèi)存,如果不用三級緩存,每次都從服務(wù)端獲取圖片的話,圖片消耗的流量就會非常多,如果把所有圖片都放在內(nèi)存的話,那就有可能發(fā)生 OOM 。

下面我們來看下 Glide 的內(nèi)存緩存原理、磁盤緩存原理和磁盤緩存策略。

4.1 Glide 內(nèi)存緩存原理

前面提到 Engine 的 load() 方法會先在內(nèi)存緩存中查找 Key 對應(yīng)的資源,沒有的話再啟動新的解碼任務(wù)。

這里說的內(nèi)存緩存就是 MemoryCache,MemoryCache 是一個接口,它的實現(xiàn)類是 LruResourceCache。

LruResourceCache 不僅實現(xiàn)了 MemoryCache 接口,而且還是 LruCache 的子類,具體的內(nèi)存緩存實現(xiàn)是在 LruCache 中。

image

在 LruCache 的 put() 方法中,首先會判斷要保存的元素大小是否大于緩存最大值,如果是的話,則不進行保存,如果不是的話,則把當前容量加上元素的大小,并把該元素放入緩存。

LruCache 比較特別的就是它的 trimToSize() 方法和 LinkedHashMap 的 accessOrder 屬性。

1. trimToSize()

LruCache 在用 put() 方法保存新的元素時,它會通過 trimToSize() 方法移除最近最少使用的元素。

2. accessOrder

LruCache 中是用 LinkedHashMap 保存數(shù)據(jù)的,并且這個 LinkedHashMap 的 accessOrder 的值為 true,也就是每一次獲取 LinkedHashMap 中的元素時,這個元素都會被移到鏈表的尾端。

4.2 Glide 磁盤緩存原理

Glide 是用 DiskCache 保存圖片文件的,DiskCache 是一個接口,這個接口中還定義了 Factory 和 Writer 兩個接口,Writer 只是對 ResourceEncoder 的封裝。

下面我們就來看看 DiskCache 和 DiskCache.Factory 的具體實現(xiàn)。

4.2.1 DiskLruCache

DiskCache 有兩個實現(xiàn)類, DiskCacheAdapter 和 DiskLruCacheWrapper,DiskCacheAdapter 只是一個空實現(xiàn)。

從名字可以看得出來 DiskLruCacheWrapper 是對 DiskLruCache 的封裝,具體的實現(xiàn)是在 DiskLruCache 中,DataCacheGenerator 和 ResourceCacheGenerator 都是用的 DiskLruCache 來獲取磁盤緩存數(shù)據(jù)的。

1. Entry

和 LruCache 一樣,DiskLruCache 中也有一個 LinkedHashMap ,這個 HashMap 的 Key 的類型為 String,Value 的類型為 Entry,從緩存中獲取到的圖片文件會放在 Entry的 cleanFiles 字段中。

2. Editor

當圖片加載進入編碼階段時,DecodeJob 會通過編碼管理器調(diào)用 DiskLruCacheWrapper 的 put() 方法保存圖片文件。

在 DiskLruCacheWrapper 的 put() 方法中,會通過 DiskCache 的緩存編輯器 Editor 獲取圖片文件,獲取到圖片文件后,就會用 Writer 把文件寫入本地,寫完后再調(diào)用 Editor 的 commit() 方法,把清理緩存的回調(diào)提交到清理線程池中。

3. 清理資源

DiskLruCache 中有一個執(zhí)行清理資源任務(wù)的線程池,線程池的線程數(shù)最多為 1,

這個線程池要執(zhí)行的任務(wù)為 cleanupCallback 回調(diào),這個回調(diào)會執(zhí)行 trimToSize() 方法,為的就是把最近最少使用的文件清除掉。

4.2.2 DiskCache.Factory

在 DiskCache 中有一個 Factory 工廠接口,這個接口用在了 Engine 的 LazyDiskCacheProvider 中。

在 Factory 接口中,定義了默認的磁盤緩存大小為 250M,默認的緩存目錄名稱為 "image_manager_disk_cache" 。

Factory 主要有下面 2 個實現(xiàn)類。

  • ExternalPreferredCacheDiskCacheFactory

    用的是 getExternalCacheDir() 。

    對應(yīng)的目錄是 /data/user/0/包名/cache/image_manager_disk_cache。

  • InternalCacheDiskCacheFactory

    用的是 context.getCacheDir() 。

    對應(yīng)的目錄是 /data/user/0/包名/cache 。

默認情況下 Glide 用的是 InternalCacheDiskCacheFactory ,如果想把圖片放在外部緩存目錄的話,可以在自定義的 GlideModule 設(shè)置 DiskCache 。

4.3 Glide 磁盤緩存策略

在加載圖片時,我們可以用 diskCacheStratgy() 方法設(shè)置圖片在磁盤的緩存策略,這個選項傳入的參數(shù)類型為抽象類 DiskCacheStrategy。

磁盤緩存策略涉及到 Glide 的數(shù)據(jù)源類型 DataSource 和編碼策略 EncodeStratefy,編碼策略前面講過了,下面我們先來看看數(shù)據(jù)源 DataSource。

4.3.1 五種數(shù)據(jù)源

Glide 中定義了下面 5 種數(shù)據(jù)源 DataSource。

  • LOCAL

    設(shè)備上有的數(shù)據(jù),比如 App 內(nèi)置的 Drawable 也屬于 LOCAL ;

  • REMOTE

    從服務(wù)端拿到的數(shù)據(jù);

  • DATA_DISK_CACHE

    從緩存中取出來的原始數(shù)據(jù);

  • RESOURCE_DISK_CACHE

    從緩存中取出來的圖片資源;

  • MEMORY_CACHE

    從內(nèi)存緩存中取出來的數(shù)據(jù);

4.3.2 四個抽象方法

DiskCacheStrategy 有下面 4 個抽象方法,這個 4 個方法的返回值都是布爾值。

  • isDataCacheable()
  • isResourceCacheable()
  • decodeCachedResource()
  • decodeCacheData()
1. isDataCacheable()

是否保存圖片的原始數(shù)據(jù)。

DecodeJob 中用到的 SourceGenerator 在從圖片來源獲取到數(shù)據(jù)后,會根據(jù)這個方法判斷是否保存圖片的原始數(shù)據(jù)。

2. isResourceCacheable()

是否保存解碼后的圖片數(shù)據(jù)。

當資源解碼器對圖片數(shù)據(jù)進行解碼后,DecodeJob 就會根據(jù)這個方法的返回值決定是否保存該 Resource 。

3. decodeCachedResource()

是否對緩存的解碼后的圖片數(shù)據(jù)進行解碼。

在 DecodeJob 的 getNextStage() 中,會根據(jù)這個方法的返回值判斷,如果返回值為 false,意味著跳過 RESOURCE_CACHE 步驟,也就是不對緩存中處理過的圖片資源進行處理。

4. decodeCachedData()

是否對緩存的原始數(shù)據(jù)進行解碼。

在 DecodeJob 的 getNextStage() 方法中,會根據(jù)這個方法的返回值判斷,如果該值為 false,意味著跳過 DATA_CACHE 步驟,也就是不對緩存中的原始圖片數(shù)據(jù)進行處理。

4.3.2 五種緩存策略

Glide 定義好的磁盤緩存策略有下面 5 種,默認為 AUTOMATIC。

  • AUTOMATIC
  • ALL
  • NONE
  • RESOURCE
  • DATA
1. AUTOMATIC
  • isDataCacheable()

    只保存網(wǎng)絡(luò)圖片的原始數(shù)據(jù);

  • isResourceCacheable()

    只保存數(shù)據(jù)源為 DATA_DISK_CACHE 或 LOCAL ,并且編碼策略為 TRANSFORMED 的圖片資源;

  • decodeCachedResource()

    true;

  • decodeCachedData()

    true;

2. ALL
  • isDataCacheable()

    只保存網(wǎng)絡(luò)圖片的原始數(shù)據(jù);

  • isResourceCacheable()

    不保存數(shù)據(jù)源為 RESOURCE_DISK_CACHE 和 MEMORY_CACHE 的圖片資源;

  • decodeCachedResource()

    true;

  • decodeCachedData()

    true;

3. DATA
  • isDataCacheable()

    不保存數(shù)據(jù)源為 DATA_DISK_CACHE 或 MEMORY_CACHE 的圖片資源;

  • isResourceCacheable()

    false;

  • decodeCachedResource()

    false;

  • decodeCachedData()

    true;

4. RESOURCE
  • isDataCacheable()

    false;

  • isResourceCacheable()

    不保存數(shù)據(jù)源為 RESOURCE_DISK_CACHE 和 MEMORY_CACHE 的圖片資源;

  • decodeCachedResource()

    true;

  • decodeCachedData()

    false;

5. NONE

所有方法的返回值都為 false。

4.4 BitmapPool

1. 減少 Bitmap 占用的內(nèi)存

BitmapResource 的 BitmapPool 用的就是 GlideBuilder 中的 BitmapPool,Downsampler 在解碼后,會把圖片放入 BitmapPool 中,當 BitmapResource 被回收時,也會把 Bitmap 放到 BitmapPool 中。

具體需要用到 BitmapPool 中的 Bitmap 的地方在 TransformationUtils 中,TransformationUtils 在進行變換前會從 BitmapPool 中獲取之前保存的 Bitmap。

之所以要這么做,是因為每一次變換都需要創(chuàng)建一個 Bitmap ,BitmapPool 就是為了復(fù)用這個 Bitmap 占用的內(nèi)存,這樣下次要做變換操作時,可以用同一個 Bitmap 就進行復(fù)用,以減少內(nèi)存使用。

比如對于 RecyclerView 中的圖片,它們的大小是一樣的,沒必要在變換時為每張圖片都創(chuàng)建一個新的 Bitmap。

2. LruPoolStrategy

BitmapPool 是一個接口,實現(xiàn)類為 LruBitmapPool ,具體的邏輯在 LruPoolStrategy 中。

LruPoolStrategy 也是一個接口,它的實現(xiàn)類為 SizeConfigStrategy,從 LruPoolStrategy 的名字可以看得出來,BitmapPool 用的是 LruCache 來保存 Bitmap 的。

在 LruPoolStrategy 中,會根據(jù) Bitmap 的大小和編碼選項,把 Bitmap 放到 GroupedLinkedHamp 中。

3.5 ArrayPool

和 BitmapPool 一樣,ArrayPool 用的也是 LruCache,也是為了減少不必要的內(nèi)存浪費。

比如在輸入流編碼器 StreamEncoder 中,當把輸入流轉(zhuǎn)化為文件時,需要創(chuàng)建一個新的字節(jié)數(shù)組,如果不用 ArrayPool,而圖片是在列表中加載的,那就會創(chuàng)建很多不必要的的字節(jié)數(shù)組。

5. Glide 初始化流程與配置

5.1 Glide 初始化流程

在看 Glide 的配置前,我們先來看下 Glide 的初始化流程,因為讀取配置就是在初始化的過程中讀取的。

5.1.1 with()

image

當我們調(diào)用 Glide.with() 方法時,Glide 會先用 getRetriever() 方法獲取請求管理器檢索器,在這個方法中還會用 get() 方法獲取 Glide 實例,獲取不到的話就會初始化 Glide 。

5.1.2 initializeGlide()

image

我們可以在 AndroidManifest 中聲明 GlideModule,也可以用 @GlideModule 注解聲明 GlideModule,走的都是上面這個流程。

1. 應(yīng)用選項

Glide 有一個 ApppModuleGenerator,它會把讀取我們設(shè)定的 AppGlideModule 中的配置,然后生成一個 GeneratedAppGlideModuleImpl 配置。

然后用反射讀取這個配置,讀取到配置后,就會應(yīng)用我們在 applyOptions() 中給 GlideBuilder 設(shè)置的選項。

如果不用生成加反射的話讀取配置的話,Glide 并不知道我們會把配置叫什么,放哪里。

2. 創(chuàng)建實例

應(yīng)用選項后,就會創(chuàng)建一個 Glide 實例。

3. 注冊組件

創(chuàng)建完實例后,就會把實例的 registry 傳到 registerComponents() 中,也就是我們修改編解碼邏輯的地方。

4. 注冊回調(diào)

Glide 實現(xiàn)了 ComponentCallback 用于監(jiān)聽內(nèi)存狀態(tài),這里的注冊回調(diào)就是調(diào)用 ApplicationContext 的 registerComponentCallbacks() 方法。

5.2 Registry

Glide 有一個登記處 Registry ,它包含了下面這些 Registry 。

  • 數(shù)據(jù)加載器登記處 ModelLoaderRegistry
  • 編碼器登記處 EncoderRegistry
  • 資源解碼器登記處 ResourceDecoderRegistry
  • 資源編碼器登記處 ResourceEncoderRegistry
  • 數(shù)據(jù)重繞器登記處 DataRewinderRegistry
  • 轉(zhuǎn)碼器登記處 DataRewinderRegistry
  • 圖片頭部信息解析器登記處 ImageHeaderRegistry

在 Glide 的構(gòu)造方法中,會把所有的編碼、解碼和數(shù)據(jù)加載等邏輯通過 Registry 的 append() 方法登記到 Registry 中,我們可以在 AppGlideModule 的 registerComponents() 方法中獲取到 registry 實例,通過這個實例就可以替換掉對應(yīng)的實現(xiàn)。

1. registerComponents()

比如獲取網(wǎng)絡(luò)圖片默認用的是 HttpUrlFetcher ,HttpUrlFetcher 是用的 HttpURLConnection 來獲取圖片數(shù)據(jù)的,我們可以在 registerComponents() 方法中,把 HttpUrlFetcher 替換為 OkHttp 。

2. Entry
image

之所以要用 Class 來替換 ModelLoader ,是因為 ModelLoaderRegistry 的 append() 方法會用來源類型(Model)、原始數(shù)據(jù)類型(Data)和 ModelLoaderFactory 來創(chuàng)建不同類型的 Entry ,這些 Entry 會保存在 MultiModelLoaderFactory 工廠中。

當 DataFetcherGenerator 通過 ModelLoader 獲取數(shù)據(jù)時,則會通過 model 的 Class 信息來獲取 ModelLoader,除了 ModelLoaderRegistry,其他的 Registry 中也有 Entry。

5.3 GlideBuilder

GlideBuilder 就是 Glide 的構(gòu)建器,它包含了下面這些數(shù)據(jù)。

  • 線程池
    • 圖片來源線程池 sourceExecutor
    • 磁盤緩存線程池 diskCacheExecutor
    • 動畫線程池 animationExecutor
  • 內(nèi)存大小計算器 memorySizeCalculator
  • 網(wǎng)絡(luò)狀態(tài)監(jiān)聽器工廠 connectivityMonitorFactory
  • 請求選項工廠 defaultRequestOptionsFactory
  • 請求管理器工廠 requestManagerFactory
  • 請求監(jiān)聽器列表 defaultRequestListeners
  • 位圖池 bitmapPool
  • 數(shù)組池 arrayPool
  • 內(nèi)存緩存 MemoryCache
  • 磁盤緩存工廠 diskCacheFactory
  • 引擎 Engine

上面這些字段大多數(shù)都是可以在 AppGlideModule 的 applyOptions() 方法中,調(diào)用 GlideBuilder 的 setXXX() 方法來替換實現(xiàn)的,下面我們主要看下 Glide 線程池和內(nèi)存大小計算器。

5.3.1 Glide 線程池

GlideExecutor 是 Glide 的線程池實現(xiàn),Glide 中有下面 4 種線程池。

1. SourceExecutor

對圖片來源解碼的任務(wù)的線程池,線程數(shù)為最多為 4 ,最小為設(shè)備的 CPU 核數(shù)。

2. unlimitedSourceExecutor

如果我們在加載圖片時調(diào)用了 useUnlimitedSourceGeneratorsPool() 選項,那 Glide 就會用這個無線程數(shù)限制的線程池來獲取圖片。

3. DiskCacheExecutor

對磁盤緩存數(shù)據(jù)解碼的任務(wù)的線程池,線程數(shù)為 1 。

4. AnimationExecutor

這也是用來從圖片來源獲取數(shù)據(jù)的線程池,而不是用來播放 GIF 動畫的線程池,當我們加載圖片時調(diào)用了 useAnimationPool(true) ,那在獲取圖片數(shù)據(jù)時 EngineJob 就會把 DecodeJob 放到 AnimationExecutor 中。

如果設(shè)備 CPU 核數(shù)大于等于 4 ,那 AnimationPool 線程數(shù)就是 2 ,否則就是 1 。

5. 自定義線程池

Glide 對于線程池只允許用 GlideBuilder 中的 Builder 來設(shè)置參數(shù),GlideExecutor.Builder 支持下面幾個參數(shù)。

  • setThreadTimeoutMillis()

    設(shè)置線程存活時間;

  • setThreadCount()

    設(shè)置線程數(shù);

  • setUncaughtThrowableStrategy()

    設(shè)置異常處理策略,有三種可以選,也可以自己自定義,默認為 LOG。

    • IGNORE

      忽略異常;

    • LOG

      打印異常日志;

    • THROW

      拋出異常;

  • setName()

    設(shè)置線程名稱;

5.3.2 內(nèi)存大小計算器

MemorySizeCalculator 負責(zé)計算 BitmapPool 、ArrayPool 和 MemoryCache 的大小。

1. BitmapPool 大小

BitmapPool 大小為一屏可容納的最高圖片質(zhì)量的大小,比如 1080 * 1920 * 4 ≈ 7.9M 。

2. ArrayPool 大小

默認為 4M,如果系統(tǒng)版本低于 19 則為 2M。

3. 內(nèi)存緩存大小

內(nèi)存緩存大小為兩屏可容納的最高圖片質(zhì)量的大小,比如 1080 * 1920 * 2 * 4 ≈ 15.8M 。

4. maxSizeMultiplier

MemorySizeCalculator 在計算 BitmapPool 和 MemoryCache 大小時,會通過 getMaxSize() 方法,用 ActivityManager 獲取 memoryClasss,然后用 memoryClass 的值乘以 maxSizeMultiplier,maxSizeMultiplier 默認為 0.4。

memoryClass 就是獲取應(yīng)用可用內(nèi)存大小,比如我的 VIVO 手機給應(yīng)用分配的可用內(nèi)存為 256M,以我的手機為例,256 * 0.4 = 102.4 ,也就是默認情況下, BitmapPool 、ArrayPool 和 MemoryCache 的大小最多不會超過 102.4M。

當 BitmapPool 、ArrayPool 和 MemoryCache 的大小加起來大于最大值時,會按這個最大值重新計算 BitmapPool 和 MemoryCache 的大小。

5. 自定義內(nèi)存大小計算方式

和 GlideExecutor 一樣,MemorySizeCalculator 也有一個 Builder,支持下面這些參數(shù)的設(shè)置。

  • setMemoryCacheSizeScreens(screens)

    設(shè)置內(nèi)存緩存大小為幾屏的大??;

  • setBitmapPoolScreens(screens)

    設(shè)置 BitmapPool 大小為幾屏的大小;

  • setArrayPoolSize(sizeBytes)

    設(shè)置 ArrayPool 大??;

  • setMaxSizeMultiplier()

    設(shè)置最大值乘數(shù)大小;

6. Glide 圖片加載選項

1. placeholder(drawable)

如果用戶打開 App 的時候,本來應(yīng)該顯示圖片的控件,由于網(wǎng)絡(luò)原因等了好幾秒都沒加載出來,這樣用戶體驗就不好,所以我們可以加上一張占位圖,這樣用戶就知道圖片等下就出來了。

這里要注意的是,占位圖只能是 App 內(nèi)置圖片,不能是網(wǎng)絡(luò)圖片,否則無網(wǎng)絡(luò)的時候它就沒作用了。

2. error(drawable)

當用戶的網(wǎng)絡(luò)出現(xiàn)錯誤,圖片加載失敗時,一直顯示占位圖,用戶就會一直等待,如果等了半天都沒加載出來,用戶就會覺得我們的 App 有問題。

這時候我們可以用一張錯誤占位圖,這樣用戶就知道有可能是網(wǎng)絡(luò)出問題了,切換一下網(wǎng)絡(luò),又或者是主動聯(lián)系開發(fā)者。

如果沒有設(shè)置這個參數(shù)的話,出錯時會顯示 placeholder 中傳的占位圖。

3. fallback()

設(shè)置當數(shù)據(jù)模型為空,也就是我們傳入 load() 中的值為空時要顯示的圖片,沒有設(shè)置 fallback 會顯示錯誤占位圖,連錯誤占位圖也沒設(shè)置就會顯示 placeholder 占位圖。

4. override(width, height)

如果我們不想讓 Glide 把圖片按 ImageView 的大小進行縮放,我們可以用這個方法來設(shè)置加載的目標寬高。

5. fitCenter()
image

fitCenter 是一個圖片裁剪選項,用于把圖片尺寸限定在 ImageView 內(nèi)并居中,這樣圖片就能完全顯示。

選擇這個選項后,當圖片的寬高比和 ImageView 的寬高比不同時,ImageView 就不會被填滿。

6. centerCrop()

當我們使用了 centerCrop (),并且圖片寬高比與 ImageView 不同時,Glide 會裁剪中間的部分,以填滿 ImageView ,這時圖像就不是完全顯示的了。

7. centerInside()

與 fitCenter 類似,不同的是,當 ImageView 的尺寸為 wrap_content 時,fitCenter() 會把圖片放大,而 centerInside() 則會保持原圖大小。

image
8. transform(Transformation)

設(shè)置變換選項,比如旋轉(zhuǎn)選項 Rotate 和圓角選項 RoundedCorners 沒有對應(yīng)的方法可以直接設(shè)置,就可以用這個方法傳進去。

如果想要同時多種變換選項,也要從這個參數(shù)傳進去,比如 transform(CenterCrop(), RoundedCorners()) ,這樣創(chuàng)建的就是多重變換 MultiTransformation ,否則只有后面設(shè)置的變換選項會起效。

9. dontTransform()

禁止變換,調(diào)用這個方法后會刪除之前設(shè)定的變換選項。

10. skipMemoryCache(boolean)

跳過內(nèi)存緩存。

默認情況下 Glide 會把圖片放在內(nèi)存緩存中,如果我們不想要讓某張圖片保留在內(nèi)存緩存中,比如加載高清原圖時,可以把這個值改為 true 。

11. diskCacheStrategy(strategy)

磁盤緩存策略,Glide 自帶了 5 種磁盤緩存策略,默認為 AUTOMATIC。

  • ALL
  • NONE
  • DATA
  • RESOURCE
  • AUTOMATIC
12. onlyRetrieveFromCache(boolean)

如果傳 true ,表示不從圖片來源獲取數(shù)據(jù),只從緩存中讀取數(shù)據(jù)。

13.priority

設(shè)置加載圖片的優(yōu)先級,比如運營圖的優(yōu)先級就比其他圖片高,Glide 的加載優(yōu)先級有下面四種。

  • IMMEDIATE

    立即加載;

  • HIGH

    高優(yōu)先級;

  • NORMAL

    正常優(yōu)先級;

  • LOW

    低優(yōu)先級;

14. sizeMultiplier(float)
image

按比例縮放圖片。

15. encodeFormat(CompressFormat)

設(shè)置下載或緩存的圖片的編碼類型,比如 JPEG、PNG、WEBP。

如果沒有設(shè)置 encodeFormat,并且圖像有透明通道,那 Glide 默認會把以 PNG 的方式保存圖片,否則就是 JPEG。

16. encodeQuality(int)

設(shè)置下載或緩存的圖片的編碼質(zhì)量,這個參數(shù)會傳到 Bitmap.compress() 方法中。

17. frame(long)

取視頻中某一幀的作為圖片。

18. format(DecodeFormat)

設(shè)置解碼格式,比如 ARGB_8888、RGB_565 。

19. timeout(milliseconds)

設(shè)置網(wǎng)絡(luò)圖片的請求超時,默認為 2500 毫秒。

20. transition(options)

設(shè)置過渡動畫,這里的 options 傳的是 TransitionOptions ,這是一個抽象類,它有三個子類。

  • GenericTransitionOptions.with()

    自定義動畫。

  • DrawableTransitionOptions.withCrossFade()

    設(shè)置圖片淡入動畫。

  • BitmapTransitionOptions.withCrossFade()

    如果調(diào)用了 asBitmap() 方法,就要用這個過渡動畫。

21. dontAnimate()

不播放 GIF 動畫 。

22. apply(options)

應(yīng)用選項。

23. listener(RequestListener)

設(shè)置請求監(jiān)聽器 ,這個接口有下面兩個回調(diào)。

  • onResourceReady()

    圖片加載完成回調(diào)。

  • onLoadFailed()

    圖片加載失敗回調(diào)。

24. asBitmap()

把解碼類型改為 Bitmap 。

這個方法并不是 BaseRequestOptions 提供的,而是 RequestManager 中的,但是對于外部使用者來說,這就是一個加載選項,不同的是,這個選項需要在 load() 方法前調(diào)用。

25.asFile()

把解碼類型改為 File,也就是用 submit().get() 獲取到的類型為 File 。

26. submit()

我們可以用 submit() 方法來獲取我們想要的圖片類型,比如文件、Bitmap 和 Drawable 等,但是要注意的是這個方法要在子線程中執(zhí)行。

27. downloadOnly()

設(shè)定只下載圖片,不加載圖片。

這個選項就是 asFile() + diskCacheStrategy(DATA) + priority(LOW) + skiptMmoeryCache(true) 。

28. download()

這個方法相當于是用 download(image) 替換 downloadOnly().load(image) 。

29. preload(width, height)

預(yù)加載圖片,可以用來提前下載一些下一次啟動應(yīng)用的時候會用到的圖片,比如閃屏頁廣告。

和 downloadOnly() 的區(qū)別在于不需要在子線程調(diào)用。

參考資料

其他

如果你想交流 Android 開發(fā)相關(guān)的問題,歡迎加我的微信 oushaoze2015 一起探討,添加時請備注“掘金”。

本文已投稿公眾號 code小生。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容