Glide
,該功能很是強大 Android
圖片加載開源框架 相信你們並不陌生 算法
正因爲他的功能強大,因此它的源碼很是複雜,這致使不少人望而卻步緩存
本人嘗試將 Glide
的功能進行分解,並單獨針對每一個功能進行源碼分析,從而下降Glide
源碼的複雜度。安全
接下來,我將推出一系列關於
Glide
的功能源碼分析,有興趣能夠繼續關注bash
Glide
的圖片緩存功能進行流程 & 源碼分析 ,但願大家會喜歡。因爲文章較長,但願讀者先收藏 & 預留足夠時間進行查看。服務器
Glide
須要緩存的 圖片資源 分爲兩類:微信
Source
) :即圖片源的圖片初始大小 & 分辨率Result
) :通過 尺寸縮放 和 大小壓縮等處理後的圖片當使用
Glide
加載圖片時,Glide默認 根據View
視圖對圖片進行壓縮 & 轉換,而不顯示原始圖(這也是Glide
加載速度高於Picasso
的緣由)網絡
Glide
的緩存功能設計成 二級緩存:內存緩存 & 硬盤緩存並非三級緩存,由於 從網絡加載 不屬於緩存app
- 內存緩存 默認開啓
Glide
中,內存緩存 & 磁盤緩存相互不影響,獨立配置
只 緩存轉換事後的圖片框架
可緩存原始圖片 & 緩存轉換事後的圖片,用戶自行設置ide
Glide
的緩存機制使得 Glide
具有很是好的圖片緩存效果,從而使得具有較高的圖片加載效率。
如,在
RecyclerView
上下滑動,而RecyclerView
中只要是Glide
加載過的圖片,均可以直接從內存中讀取 & 展現,從而不須要重複從 網絡或硬盤上讀取,提升圖片加載效率。
Glide
的緩存功能分爲:內存緩存 & 磁盤緩存只 緩存轉換事後的圖片,而並不是原始圖片
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
進行垃圾回收時,一旦發現弱引用對象,都會進行回收(不管內存充足否)
可緩存原始圖片 & 緩存轉換事後的圖片,用戶自行設置
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
工具類
Glide
整個緩存流程 從 加載圖片請求 開始,其中過程 有本文最關注的 內存緩存的讀取 & 寫入、磁盤緩存的讀取 & 寫入下面,我將根據 Glide
緩存流程中的每一個步驟 進行源碼分析。
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
生成完畢。
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
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;
}
...
}
複製代碼
若上述兩個方法都沒獲取到緩存圖片時(即內存緩存裏沒有該圖片的緩存),就開啓新線程加載圖片。
若沒法從 內存緩存 裏 得到緩存的圖片,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();
}
}
複製代碼
若沒法從 內存緩存 裏 得到緩存的圖片,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. 先將圖片數據 轉換 再 解碼,最終返回
}
複製代碼
Glide
兩級緩存機制裏都沒有該圖片緩存時,只能去源頭(如網絡)去加載圖片了
- 此處先忽略該過程 #2. 如有興趣的同窗請看#該過程在請看文章
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多個參數
}
複製代碼
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
算法機制的區域 & 一塊使用了 弱引用機制的緩存- 內存緩存只緩存 轉換後的圖片
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);
}
...
}
複製代碼
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上述代碼的標記一、標記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
算法 的內存緩存至此,Glide
的圖片緩存流程解析完畢。
Glide
的圖片緩存流程 彙總關於內存緩存 的總結
LruCache
算法機制的內存緩存讀取,再從弱引用機制的 內存緩存 讀取LruCache
算法機制的內存緩存關於磁盤緩存 的總結
是否讀取 取決於
Glide
使用API的設置
是否寫入 取決於
Glide
使用API的設置
Glide
實現內存 & 磁盤緩存是根據 圖片的緩存Key
進行惟一標識如 七牛雲 等等。
Url
地址的基礎上再加一個token參數http://url.com/image.jpg?token=a6cvva6b02c670b0a
複製代碼
Glide
加載該圖片時,會使用加了token
參數的圖片Url
地址 做爲 id
參數,從而生成 緩存Keytoken
參數可能會發生變化,並非一成不變token
參數變了,則圖片Url
跟着變,則生成緩存key的所需id參數發生變化,即 緩存Key也會跟着變化token
參數變化,而致使緩存Key發生變化,從而使得 Glide
的緩存功能失效緩存Key發生變化,即同一個圖片的當前緩存key 和 以前寫入緩存的key不相同,這意味着 在讀取緩存時 沒法根據當前緩存key 找到以前的緩存,從而使得失效
具體請看文章:Android 圖片加載的那些事:爲何你的Glide 緩存沒有起做用?
Glide
的圖片緩存功能進行流程 & 源碼分析Glide
的其餘功能進行源碼分析 ,有興趣能夠繼續關注Carson_Ho的安卓開發筆記