前言
-
Glide
,該功能非常強大Android
圖片載入開源框架 相信大家並不陌生 -
正由於他的功能強大,所以它的原始碼非常複雜,這導致很多人望而卻步
-
本人嘗試將
Glide
的功能進行分解,並單獨針對每個功能進行原始碼分析,從而降低Glide
原始碼的複雜度。
接下來,我將推出一系列關於
Glide
的功能原始碼分析,有興趣可以繼續關注
- 今天,我將主要針對
Glide
的圖片快取功能進行流程 & 原始碼分析 ,希望你們會喜歡。
由於文章較長,希望讀者先收藏 & 預留足夠時間進行檢視。
目錄
1. Glide快取機制簡介
1.1 快取的圖片資源
Glide
需要快取的 圖片資源 分為兩類:
- 原始圖片(
Source
) :即圖片源的圖片初始大小 & 解析度 - 轉換後的圖片(
Result
) :經過 尺寸縮放 和 大小壓縮等處理後的圖片
當使用
Glide
載入圖片時,Glide預設 根據View
檢視對圖片進行壓縮 & 轉換,而不顯示原始圖(這也是Glide
載入速度高於Picasso
的原因)
1.2 快取機制設計
Glide
的快取功能設計成 二級快取:記憶體快取 & 硬碟快取
並不是三級快取,因為 從網路載入 不屬於快取
- 快取讀取順序:記憶體快取 --> 磁碟快取 --> 網路
- 記憶體快取 預設開啟
Glide
中,記憶體快取 & 磁碟快取相互不影響,獨立配置
- 二級快取的作用不同:
- 記憶體快取:防止應用 重複將圖片資料 讀取到記憶體當中
只 快取轉換過後的圖片
- 硬碟快取:防止應用 重複從網路或其他地方重複下載和讀取資料
可快取原始圖片 & 快取轉換過後的圖片,使用者自行設定
Glide
的快取機制使得 Glide
具備非常好的圖片快取效果,從而使得具備較高的圖片載入效率。
如,在
RecyclerView
上下滑動,而RecyclerView
中只要是Glide
載入過的圖片,都可以直接從記憶體中讀取 & 展示,從而不需要重複從 網路或硬碟上讀取,提高圖片載入效率。
2. Glide 快取功能介紹
Glide
的快取功能分為:記憶體快取 & 磁碟快取- 具體介紹如下
2.1 記憶體快取
- 作用:防止應用 重複將圖片資料 讀取到記憶體當中
只 快取轉換過後的圖片,而並非原始圖片
- 具體使用
預設情況下,
Glide
自動開啟 記憶體快取
// 預設開啟記憶體快取,使用者不需要作任何設定
Glide.with(this)
.load(url)
.into(imageView);
// 可通過 API 禁用 記憶體快取功能
Glide.with(this)
.load(url)
.skipMemoryCache(true) // 禁用 記憶體快取
.into(imageView);
複製程式碼
- 實現原理
Glide
的記憶體快取實現是基於:LruCache
演算法(Least Recently Used
) & 弱引用機制
LruCache
演算法原理:將 最近使用的物件 用強引用的方式 儲存在LinkedHashMap
中 ;當快取滿時 ,將最近最少使用的物件從記憶體中移除- 弱引用:弱引用的物件具備更短生命週期,因為 **當
JVM
進行垃圾回收時,一旦發現弱引用物件,都會進行回收(無論記憶體充足否)
2.2 磁碟快取
- 作用:防止應用 重複從網路或其他地方重複下載和讀取資料
可快取原始圖片 & 快取轉換過後的圖片,使用者自行設定
- 具體使用
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
// 快取引數說明
// DiskCacheStrategy.NONE:不快取任何圖片,即禁用磁碟快取
// DiskCacheStrategy.ALL :快取原始圖片 & 轉換後的圖片
// DiskCacheStrategy.SOURCE:只快取原始圖片(原來的全解析度的影象,即不快取轉換後的圖片)
// DiskCacheStrategy.RESULT:(預設)只快取轉換後的圖片(即最終的影象:降低解析度後 / 或者轉換後 ,不快取原始圖片
複製程式碼
- 實現原理
使用
Glide
自定義的DiskLruCache
演算法
- 該演算法基於
Lru
演算法中的DiskLruCache
演算法,具體應用在磁碟快取的需求場景中- 該演算法被封裝到
Glide
自定義的工具類中(該工具類基於Android
提供的DiskLruCache
工具類
3. Glide 快取流程 解析
Glide
整個快取流程 從 載入圖片請求 開始,其中過程 有本文最關注的 記憶體快取的讀取 & 寫入、磁碟快取的讀取 & 寫入- 具體如下
下面,我將根據 Glide
快取流程中的每個步驟 進行原始碼分析。
4. 快取流程 原始碼分析
步驟1:生成快取Key
Glide
實現記憶體 & 磁碟快取 是根據 圖片的快取Key 進行唯一標識
即根據 圖片的快取Key 去快取區找 對應的快取圖片
- 生成快取
Key
的程式碼發生在Engine
類的load()
中
#該程式碼在上一篇文章當中已分析過,只是當時忽略了快取相關的內容,現在僅貼出快取相關的程式碼
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
final String id = fetcher.getId();
// 獲得了一個id字串,即需載入圖片的唯一標識
// 如,若圖片的來源是網路,那麼該id = 這張圖片的url地址
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());
// Glide的快取Key生成規則複雜:根據10多個引數生成
// 將該id 和 signature、width、height等10個引數一起傳入到快取Key的工廠方法裡,最終建立出一個EngineKey物件
// 建立原理:通過重寫equals() 和 hashCode(),保證只有傳入EngineKey的所有引數都相同情況下才認為是同一個EngineKey物件
// 該EngineKey 即Glide中圖片的快取Key
...
}
複製程式碼
至此,Glide
的圖片快取 Key
生成完畢。
步驟2:建立快取物件 LruResourceCache
-
LruResourceCache
物件是在建立Glide
物件時建立的 -
#而 建立
Glide
物件則是在上篇文章 講解Glide
圖片載入功能時 第2步load()
中loadGeneric()
建立ModelLoader
物件時建立的 -
請看原始碼分析
<-- 第2步load()中的loadGeneric()-->
private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
...
ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
// 建立第1個ModelLoader物件;作用:載入圖片
// Glide會根據load()方法傳入不同型別引數,得到不同的ModelLoader物件
// 此處傳入引數是String.class,因此得到的是StreamStringLoader物件(實現了ModelLoader介面)
// Glide.buildStreamModelLoader()分析 ->>分析1
<--分析1:Glide.buildStreamModelLoader() -->
public class Glide {
public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass,
Context context) {
if (modelClass == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Unable to load null model, setting placeholder only");
}
return null;
}
return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass);
// 建立ModelLoader物件時,呼叫Glide.get() 建立Glide物件-->分析2
}
<--分析2:Glide.get() -->
// 作用:採用單例模式建立Glide物件
public static Glide get(Context context) {
// 實現單例功能
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
GlideBuilder builder = new GlideBuilder(applicationContext);
for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
glide = builder.createGlide();
// 通過建造者模式建立Glide物件 ->>分析3
for (GlideModule module : modules) {
module.registerComponents(applicationContext, glide);
}
}
}
}
return glide;
}
}
<--分析3:builder.createGlide() -->
// 作用:建立Glide物件
public class GlideBuilder {
...
Glide createGlide() {
MemorySizeCalculator calculator = new MemorySizeCalculator(context);
if (bitmapPool == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
int size = calculator.getBitmapPoolSize();
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
if (memoryCache == null) {
memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
// 建立一個LruResourceCache物件 並 賦值到memoryCache物件
// 該LruResourceCache物件 = Glide實現記憶體快取的LruCache物件
}
return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
}
}
複製程式碼
至此,建立好了快取物件LruResourceCache
步驟3:從 記憶體快取 中獲取快取圖片
Glide
在圖片載入前就會從 記憶體快取 中獲取快取圖片- 讀取記憶體快取程式碼 是在
Engine
類的load()
中
即上面講解的生成快取
Key
的地方
- 原始碼分析
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread();
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
// 上面講解的生成圖片快取Key
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
// 呼叫loadFromCache()獲取記憶體快取中的快取圖片
if (cached != null) {
cb.onResourceReady(cached);
}
// 若獲取到,就直接呼叫cb.onResourceReady()進行回撥
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active);
}
// 若沒獲取到,就繼續呼叫loadFromActiveResources()獲取快取圖片
// 獲取到也直接回撥
// 若上述兩個方法都沒有獲取到快取圖片,就開啟一個新的執行緒準備載入圖片
// 即從上文提到的 Glide最基礎功能:圖片載入
EngineJob current = jobs.get(key);
return new LoadStatus(cb, current);
}
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(runnable);
return new LoadStatus(cb, engineJob);
}
...
}
複製程式碼
即:
Glide
將 記憶體快取 劃分為兩塊:一塊使用了LruCache
演算法 機制;另一塊使用了弱引用 機制- 當 獲取 記憶體快取 時,會通過兩個方法分別從上述兩塊區域進行快取獲取
loadFromCache()
:從 使用了LruCache
演算法機制的記憶體快取獲取 快取loadFromActiveResources()
:從 使用了 弱引用機制的記憶體快取獲取 快取
原始碼分析如下:
// 這2個方法屬於 Engine 類
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
<-- 方法1:loadFromCache() -->
// 原理:使用了 LruCache演算法
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
// 若isMemoryCacheable = false就返回null,即記憶體快取被禁用
// 即 記憶體快取是否禁用的API skipMemoryCache() - 請回看記憶體快取的具體使用
// 若設定skipMemoryCache(true),此處的isMemoryCacheable就等於false,最終返回Null,表示記憶體快取已被禁用
}
EngineResource<?> cached = getEngineResourceFromCache(key);
// 獲取圖片快取 ->>分析4
// 從分析4回來看這裡:
if (cached != null) {
cached.acquire();
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
// 將獲取到的快取圖片儲存到activeResources當中
// activeResources = 一個弱引用的HashMap:用於快取正在使用中的圖片
// 好處:保護這些圖片不會被LruCache演算法回收掉。 ->>方法2
}
return cached;
}
<<- 分析4:getEngineResourceFromCache() ->>
// 作用:獲取圖片快取
// 具體過程:根據快取Key 從cache中 取值
// 注:此處的cache物件 = 在構建Glide物件時建立的LruResourceCache物件,即說明使用的是LruCache演算法
private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);
// 當從LruResourceCache中獲取到快取圖片後,會將它從快取中移除->>回到方法1原處
final EngineResource result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource) cached;
} else {
result = new EngineResource(cached, true /*isCacheable*/);
}
return result;
}
<-- 方法2:loadFromActiveResources() -->
// 原理:使用了 弱引用機制
// 具體過程:當在方法1中無法獲取記憶體快取中的快取圖片時,就會從activeResources中取值
// activeResources = 一個弱引用的HashMap:用於快取正在使用中的圖片
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = null;
WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
if (activeRef != null) {
active = activeRef.get();
if (active != null) {
active.acquire();
} else {
activeResources.remove(key);
}
}
return active;
}
...
}
複製程式碼
若上述兩個方法都沒獲取到快取圖片時(即記憶體快取裡沒有該圖片的快取),就開啟新執行緒載入圖片。
- 至此,獲取記憶體快取 的步驟講解完畢。
- 總結
步驟4:開啟 載入圖片 執行緒
-
若無法從 記憶體快取 裡 獲得快取的圖片,
Glide
就會開啟 載入圖片的執行緒 -
但在該執行緒開啟後,
Glide
並不會馬上去網路 載入圖片,而是採取採用Glide
的第2級快取:磁碟快取 去獲取快取圖片 -
從 上篇文章:Android:這是一份全面 & 詳細的圖片載入庫Glide原始碼分析中,在第3步
into()
中開啟圖片執行緒run()
裡的decode()
開始(上文的分析13)
private Resource<?> decode() throws Exception {
// 在執行 載入圖片 執行緒時(即載入圖片時),分兩種情況:
// 情況1:從磁碟快取當中讀取圖片(預設情況下Glide會優先從快取當中讀取,沒有才會去網路源讀取圖片)
// 情況2:不從磁碟快取中讀取圖片
// 情況1:從磁碟快取中讀取快取圖片
if (isDecodingFromCache()) {
// 取決於在使用API時是否開啟,若採用DiskCacheStrategy.NONE,即不快取任何圖片,即禁用磁碟快取
return decodeFromCache();
// 讀取磁碟快取的入口就是這裡,此處主要講解 ->>直接看步驟4的分析9
} else {
// 情況2:不從磁碟快取中讀取圖片
// 即上文討論的從網路讀取圖片,此處不作過多描述
return decodeFromSource();
}
}
複製程式碼
步驟5:從 磁碟快取 中獲取快取圖片
若無法從 記憶體快取 裡 獲得快取的圖片,Glide
就會採用第2級快取:磁碟快取 去獲取快取圖片
<--分析9:decodeFromCache() -->
private Resource<?> decodeFromCache() throws Exception {
Resource<?> result = null;
result = decodeJob.decodeResultFromCache();
// 獲取磁碟快取時,會先獲取 轉換過後圖片 的快取
// 即在使用磁碟快取時設定的模式,如果設定成DiskCacheStrategy.RESULT 或DiskCacheStrategy.ALL就會有該快取
// 下面來分析decodeResultFromCache() ->>分析10
}
if (result == null) {
result = decodeJob.decodeSourceFromCache();
// 如果獲取不到 轉換過後圖片 的快取,就獲取 原始圖片 的快取
// 即在使用磁碟快取時設定的模式,如果設定成DiskCacheStrategy.SOURCE 或DiskCacheStrategy.ALL就會有該快取
// 下面來分析decodeSourceFromCache() ->>分析12
}
return result;
}
<--分析10:decodeFromCache() -->
public Resource<Z> decodeResultFromCache() throws Exception {
if (!diskCacheStrategy.cacheResult()) {
return null;
}
Resource<T> transformed = loadFromCache(resultKey);
// 1. 根據完整的快取Key(由10個引數共同組成,包括width、height等)獲取快取圖片
// ->>分析11
Resource<Z> result = transcode(transformed);
return result;
// 2. 直接將獲取到的圖片 資料解碼 並 返回
// 因為圖片已經轉換過了,所以不需要再作處理
// 回到分析9原處
}
<--分析11:decodeFromCache() -->
private Resource<T> loadFromCache(Key key) throws IOException {
File cacheFile = diskCacheProvider.getDiskCache().get(key);
// 1. 呼叫getDiskCache()獲取Glide自己編寫的DiskLruCache工具類例項
// 2. 呼叫上述例項的get() 並 傳入完整的快取Key,最終得到硬碟快取的檔案
if (cacheFile == null) {
return null;
// 如果檔案為空就返回null
}
Resource<T> result = null;
try {
result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
} finally {
if (result == null) {
diskCacheProvider.getDiskCache().delete(key);
}
}
return result;
// 如果檔案不為空,則將它解碼成Resource物件後返回
// 回到分析10原處
}
<--分析12:decodeFromCache() -->
public Resource<Z> decodeSourceFromCache() throws Exception {
if (!diskCacheStrategy.cacheSource()) {
return null;
}
Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
// 1. 根據快取Key的OriginalKey來獲取快取圖片
// 相比完整的快取Key,OriginalKey只使用了id和signature兩個引數,而忽略了大部分的引數
// 而signature引數大多數情況下用不到,所以基本是由id(也就是圖片url)來決定的Original快取Key
// 關於loadFromCache()同分析11,只是傳入的快取Key不一樣
return transformEncodeAndTranscode(decoded);
// 2. 先將圖片資料 轉換 再 解碼,最終返回
}
複製程式碼
- 至此,硬碟快取讀取的原始碼分析完畢。
- 總結
步驟6:從網路獲取 圖片資源
- 在
Glide
兩級快取機制裡都沒有該圖片快取時,只能去源頭(如網路)去載入圖片了 - 但從網路載入圖片前,需要先獲取該圖片的網路資源
- 此處先忽略該過程 #2. 若有興趣的同學請看#該過程在請看文章
步驟7:寫入 磁碟快取
-
Glide
將圖片寫入 磁碟快取的時機:獲取圖片資源後 、圖片載入完成前 -
寫入磁碟快取又分為:將原始圖片 寫入 或 將轉換後的圖片寫入磁碟快取
- 從 上篇文章:Android:這是一份全面 & 詳細的圖片載入庫Glide原始碼分析中,在第3步
into()
中執行圖片執行緒run()
裡的decode()
開始(上文的分析13) 此處重新貼出程式碼
private Resource<?> decode() throws Exception {
// 在執行 載入圖片 執行緒時(即載入圖片時),分兩種情況:
// 情況1:從磁碟快取當中讀取圖片(預設情況下Glide會優先從快取當中讀取,沒有才會去網路源讀取圖片)
// 情況2:不從磁碟快取中讀取圖片
// 情況1:從磁碟快取中讀取快取圖片
if (isDecodingFromCache()) {
return decodeFromCache();
// 讀取磁碟快取的入口就是這裡,上面已經講解
} else {
// 情況2:不從磁碟快取中讀取圖片
// 即上文討論的從網路讀取圖片,不採用快取
// 寫入磁碟快取就是在 此處 寫入的 ->>分析13
return decodeFromSource();
}
}
<--分析13:decodeFromSource() -->
public Resource<Z> decodeFromSource() throws Exception {
Resource<T> decoded = decodeSource();
// 解析圖片
// 寫入原始圖片 磁碟快取的入口 ->>分析14
// 從分析16回來看這裡
return transformEncodeAndTranscode(decoded);
// 對圖片進行轉碼
// 寫入 轉換後圖片 磁碟快取的入口 ->>分析17
}
<--分析14:decodeSource() -->
private Resource<T> decodeSource() throws Exception {
Resource<T> decoded = null;
try {
final A data = fetcher.loadData(priority);
// 讀取圖片資料
if (isCancelled) {
return null;
}
decoded = decodeFromSourceData(data);
// 對圖片進行解碼 ->>分析15
} finally {
fetcher.cleanup();
}
return decoded;
}
<--分析15:decodeFromSourceData() -->
private Resource<T> decodeFromSourceData(A data) throws IOException {
final Resource<T> decoded;
// 判斷是否允許快取原始圖片
// 即在使用 硬碟快取API時,是否採用DiskCacheStrategy.ALL 或 DiskCacheStrategy.SOURCE
if (diskCacheStrategy.cacheSource()) {
decoded = cacheAndDecodeSourceData(data);
// 若允許快取原始圖片,則呼叫cacheAndDecodeSourceData()進行原始圖片的快取 ->>分析16
} else {
long startTime = LogTime.getLogTime();
decoded = loadProvider.getSourceDecoder().decode(data, width, height);
}
return decoded;
}
<--分析16:cacheAndDecodeSourceData -->
private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
...
diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
// 1. 呼叫getDiskCache()獲取DiskLruCache例項
// 2. 呼叫put()寫入硬碟快取
// 注:原始圖片的快取Key是用的getOriginalKey(),即只有id & signature兩個引數
// 請回到分析13
}
<--分析17:transformEncodeAndTranscode() -->
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
Resource<T> transformed = transform(decoded);
// 1. 對圖片進行轉換
writeTransformedToCache(transformed);
// 2. 將 轉換過後的圖片 寫入到硬碟快取中 -->分析18
Resource<Z> result = transcode(transformed);
return result;
}
<-- 分析18:TransformedToCache() -->
private void writeTransformedToCache(Resource<T> transformed) {
if (transformed == null || !diskCacheStrategy.cacheResult()) {
return;
}
diskCacheProvider.getDiskCache().put(resultKey, writer);
// 1. 呼叫getDiskCache()獲取DiskLruCache例項
// 2. 呼叫put()寫入硬碟快取
// 注:轉換後圖片的快取Key是用的完整的resultKey,即含10多個引數
}
複製程式碼
- 至此,硬碟快取的寫入分析完畢。
- 總結
步驟9:寫入 記憶體快取
-
Glide
將圖片寫入 記憶體快取的時機:圖片載入完成後 、圖片顯示出來前 -
寫入 記憶體快取 的具體地方:上篇文章中當圖片載入完成後,會在
EngineJob
中通過Handler
傳送一條訊息將執行邏輯切回到主執行緒當中,從而執行handleResultOnMainThread()
裡
class EngineJob implements EngineRunnable.EngineRunnableManager {
private final EngineResourceFactory engineResourceFactory;
...
private void handleResultOnMainThread() {
...
// 關注1:寫入 弱引用快取
engineResource = engineResourceFactory.build(resource, isCacheable);
listener.onEngineJobComplete(key, engineResource);
// 關注2:寫入 LruCache演算法的快取
engineResource.acquire();
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource);
}
}
engineResource.release();
}
複製程式碼
寫入 記憶體快取分為:寫入 弱引用快取 & LruCache
演算法的快取
- 記憶體快取分為:一塊使用了
LruCache
演算法機制的區域 & 一塊使用了 弱引用機制的快取- 記憶體快取只快取 轉換後的圖片
關注1:寫入 弱引用快取
class EngineJob implements EngineRunnable.EngineRunnableManager {
private final EngineResourceFactory engineResourceFactory;
...
private void handleResultOnMainThread() {
...
// 寫入 弱引用快取
engineResource = engineResourceFactory.build(resource, isCacheable);
// 建立一個包含圖片資源resource的EngineResource物件
listener.onEngineJobComplete(key, engineResource);
// 將上述建立的EngineResource物件傳入到Engine.onEngineJobComplete() ->>分析6
// 寫入LruCache演算法的快取(先忽略)
engineResource.acquire();
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource);
}
}
engineResource.release();
}
<<- 分析6:onEngineJobComplete()() ->>
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
// 將 傳進來的EngineResource物件 新增到activeResources()中
// 即寫入了弱引用 記憶體快取
}
}
jobs.remove(key);
}
...
}
複製程式碼
關注2:寫入 LruCache演算法 快取
class EngineJob implements EngineRunnable.EngineRunnableManager {
private final EngineResourceFactory engineResourceFactory;
...
private void handleResultOnMainThread() {
...
// 寫入 弱引用快取(忽略)
engineResource = engineResourceFactory.build(resource, isCacheable);
listener.onEngineJobComplete(key, engineResource);
// 寫入 LruCache演算法的快取
engineResource.acquire();
// 標記1
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
// 標記2
cb.onResourceReady(engineResource);
}
}
engineResource.release();
// 標記3
}
複製程式碼
寫入 LruCache
演算法 記憶體快取的原理:包含圖片資源resource
的EngineResource
物件的一個引用機制:
- 用 一個
acquired
變數 記錄圖片被引用的次數 - 載入圖片時:呼叫
acquire()
,變數加1
上述程式碼的標記1、標記2 & 下面
acquire()
原始碼
<-- 分析7:acquire() -->
void acquire() {
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call acquire on the main thread");
}
++acquired;
// 當呼叫acquire()時,acquired變數 +1
}
複製程式碼
- 不載入圖片時,呼叫
release()
時,變數減1
上述程式碼的標記3 & 下面
release()
原始碼
<-- 分析8:release() -->
void release() {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
listener.onResourceReleased(key, this);
// 當呼叫acquire()時,acquired變數 -1
// 若acquired變數 = 0,即說明圖片已經不再被使用
// 呼叫listener.onResourceReleased()釋放資源
// 該listener = Engine物件,Engine.onResourceReleased()->>分析9
}
}
}
<-- 分析9:onResourceReleased() -->
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
// 步驟1:將快取圖片從activeResources弱引用快取中移除
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
// 步驟2:將該圖片快取放在LruResourceCache快取中
} else {
resourceRecycler.recycle(resource);
}
}
...
}
複製程式碼
所以:
- 當
acquired
變數 >0 時,說明圖片正在使用,即該圖片快取繼續存放到activeResources
弱引用快取中 - 當
acquired
變數 = 0,即說明圖片已經不再被使用,就將該圖片的快取Key從activeResources
弱引用快取中移除,並存放到LruResourceCache
快取中
至此,實現了:
- 正在使用中的圖片 採用 弱引用 的記憶體快取
- 不在使用中的圖片 採用
LruCache
演算法 的記憶體快取
總結
步驟10:顯示圖片
- 在將圖片 寫入 記憶體快取 & 磁碟快取後,圖片最終顯示出來
- 在下次載入時,將通過二級快取 從而提高圖片載入效率
至此,Glide
的圖片快取流程解析完畢。
5. 彙總
- 用一張圖將整個
Glide
的圖片快取流程 彙總
-
關於記憶體快取 的總結
- 讀取 記憶體快取 時,先從
LruCache
演算法機制的記憶體快取讀取,再從弱引用機制的 記憶體快取 讀取 - 寫入 記憶體快取 時,先寫入 弱引用機制 的記憶體快取,等到圖片不再被使用時,再寫入到
LruCache
演算法機制的記憶體快取
- 讀取 記憶體快取 時,先從
-
關於磁碟快取 的總結
- 讀取 磁碟快取 時,先讀取 轉換後圖片 的快取,再讀取 原始圖片 的快取
是否讀取 取決於
Glide
使用API的設定
- 寫入 磁碟快取 時,先寫入 原始圖片 的記憶體快取,再寫入的記憶體快取
是否寫入 取決於
Glide
使用API的設定
6. 額外注意:為什麼你的Glide快取功能不起作用?
a. 背景
Glide
實現記憶體 & 磁碟快取是根據 圖片的快取Key
進行唯一標識- 開發者為了降低成本 & 安全,往往會將圖片存放在雲伺服器上
如 七牛雲 等等。
- 為了保護 客戶的圖片資源,圖片雲伺服器 會在圖片
Url
地址的基礎上再加一個token引數
http://url.com/image.jpg?token=a6cvva6b02c670b0a
複製程式碼
Glide
載入該圖片時,會使用加了token
引數的圖片Url
地址 作為id
引數,從而生成 快取Key
b. 問題
- 作為身份認證的
token
引數可能會發生變化,並不是一成不變 - 若
token
引數變了,則圖片Url
跟著變,則生成快取key的所需id引數發生變化,即 快取Key也會跟著變化 - 這導致同一張圖片,但因為
token
引數變化,而導致快取Key發生變化,從而使得Glide
的快取功能失效
快取Key發生變化,即同一個圖片的當前快取key 和 之前寫入快取的key不相同,這意味著 在讀取快取時 無法根據當前快取key 找到之前的快取,從而使得失效
c. 解決方案
具體請看文章:Android 圖片載入的那些事:為什麼你的Glide 快取沒有起作用?
7. 總結
- 本文主要對
Glide
的圖片快取功能進行流程 & 原始碼分析 - 下面我將繼續對
Glide
的其他功能進行原始碼分析 ,有興趣可以繼續關注Carson_Ho的安卓開發筆記