說一下Glide緩存的大概流程
Glide會(huì)在開始一個(gè)新的圖片請(qǐng)求之前檢查以下多級(jí)緩存:
Active Resources: this image displayed in another View right now(該圖片正在被其它view展示)
Memory Cache: this image recently loaded and still in memory(該圖片最近被加載到內(nèi)存中并且還未釋放)
Resource: this image been decoded, transformed, and written to the disk cache before(該圖片已經(jīng)被解碼,轉(zhuǎn)換過,并且寫到了磁盤緩存中)
Data: the data this image was obtained from written to the disk cache before(該圖片的原始數(shù)據(jù)已經(jīng)被寫入磁盤緩存中)
ActiveResources
ActiveResources是第一級(jí)緩存。當(dāng)資源加載成功或者通過其它緩存命中,都會(huì)加到ActiveResources 中,當(dāng)資源釋放時(shí)再移除。ActiveResources 用一個(gè) Map 來保存資源的 WeakReference:
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
使用 WeakReference 的原因是防止內(nèi)存泄漏。但由于是 WeakReference,因此隨時(shí)有可能被系統(tǒng)GC,因此ActiveResources還有一個(gè) ReferenceQueue 來跟蹤資源被 GC 的情況:
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
每當(dāng) activeEngineResources 中添加一個(gè) WeakReference 都會(huì)把它和這個(gè) ReferenceQueue 關(guān)聯(lián)起來,這個(gè)體現(xiàn)在 ActiveResources#activate 方法中:
synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
相關(guān)問題:
1. 具體說一下 ActiveResources 是怎樣利用 ReferenceQueue 去跟蹤 WeakReference 是否被GC 的?
先說一下為什么ReferenceQueue 可以跟蹤 WeakReference 的GC情況。
WeakReference 有一個(gè)包含2個(gè)參數(shù)的構(gòu)造方法:
/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
其中一個(gè)就是 ReferenceQueue。這個(gè) ReferenceQueue 的作用在 WeakReference 這個(gè)類頂部的注釋中有描述:
/**
* Weak reference objects, which do not prevent their referents from being
* made finalizable, finalized, and then reclaimed. Weak references are most
* often used to implement canonicalizing mappings.
*
* <p> Suppose that the garbage collector determines at a certain point in time
* that an object is <a href="package-summary.html#reachability">weakly
* reachable</a>. At that time it will atomically clear all weak references to
* that object and all weak references to any other weakly-reachable objects
* from which that object is reachable through a chain of strong and soft
* references. At the same time it will declare all of the formerly
* weakly-reachable objects to be finalizable. At the same time or at some
* later time it will enqueue those newly-cleared weak references that are
* registered with reference queues.
*
* @author Mark Reinhold
* @since 1.2
*/
第二段中提到,在發(fā)生GC的時(shí)候,garbage collector 會(huì)把該對(duì)象的所有 weak reference 清除,而這些被清除的 WeakReference 對(duì)象在GC的同時(shí)或者稍晚一點(diǎn)的時(shí)間點(diǎn)被加入到他們注冊(cè)的ReferenceQueue 中,也就是上面那個(gè)構(gòu)造函數(shù)中傳入的第二個(gè)參數(shù)。我們只需要遍歷 ReferenceQueue 就可以知道哪些WeakReference 已經(jīng)被GC了,然后從 activeEngineResources 中移除這些資源。
那么 ActiveResources 是在什么時(shí)機(jī)去檢查ReferenceQueue 的呢?實(shí)際上在ActiveResources 對(duì)象被創(chuàng)建的時(shí)候,會(huì)起一個(gè)線程專門去檢測(cè)是否有資源被GC:
ActiveResources(
boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
monitorClearedResourcesExecutor.execute(
new Runnable() {
@Override
public void run() {
cleanReferenceQueue();
}
});
}
@Synthetic
void cleanReferenceQueue() {
while (!isShutdown) {
try {
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
cleanupActiveReference(ref);
// This section for testing only.
DequeuedResourceCallback current = cb;
if (current != null) {
current.onResourceDequeued();
}
// End for testing only.
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@Synthetic
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
// Fixes a deadlock where we normally acquire the Engine lock and then the ActiveResources lock
// but reverse that order in this one particular test. This is definitely a bit of a hack...
synchronized (listener) {
synchronized (this) {
activeEngineResources.remove(ref.key);
if (!ref.isCacheable || ref.resource == null) {
return;
}
EngineResource<?> newResource =
new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
newResource.setResourceListener(ref.key, listener);
listener.onResourceReleased(ref.key, newResource);
}
}
}
這個(gè)線程中會(huì)調(diào)用 cleanReferenceQueue 方法,在這個(gè)方法中又調(diào)用了 cleanupActiveReference,就是在這個(gè)方法中,activeEngineResources 刪除已經(jīng)被GC 的資源。
2. Reference 的種類有哪些?各自的使用場景是什么?
LruBitmapPool
/**
* An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation that uses an
* {@link com.bumptech.glide.load.engine.bitmap_recycle.LruPoolStrategy} to bucket {@link Bitmap}s
* and then uses an LRU eviction policy to evict {@link android.graphics.Bitmap}s from the least
* recently used bucket in order to keep the pool below a given maximum size limit.
*/
Glide 自己定義了一個(gè) LruBitmapPool 對(duì)象來管理緩存的 Bitmap 對(duì)象。但是具體實(shí)現(xiàn)LRU算法的是 SizeConfigStrategy,它實(shí)現(xiàn)了 LruPoolStrategy 接口。
SizeConfigStrategy
/**
* Keys {@link android.graphics.Bitmap Bitmaps} using both
* {@link android.graphics.Bitmap#getAllocationByteCount()} and the
* {@link android.graphics.Bitmap.Config} returned from
* {@link android.graphics.Bitmap#getConfig()}.
*
* <p> Using both the config and the byte size allows us to safely re-use a greater variety of
* {@link android.graphics.Bitmap Bitmaps}, which increases the hit rate of the pool and therefore
* the performance of applications. This class works around #301 by only allowing re-use of
* {@link android.graphics.Bitmap Bitmaps} with a matching number of bytes per pixel. </p>
*/
SizeConfigStrategy 利用一個(gè) GroupedLinkedMap 來存儲(chǔ) bitmap,這是一個(gè)Glide 自定義的類似 LinkedHashMap 的數(shù)據(jù)結(jié)構(gòu):
/**
* Similar to {@link java.util.LinkedHashMap} when access ordered except that it is access ordered
* on groups of bitmaps rather than individual objects. The idea is to be able to find the LRU
* bitmap size, rather than the LRU bitmap object. We can then remove bitmaps from the least
* recently used size of bitmap when we need to reduce our cache size.
*
* For the purposes of the LRU, we count gets for a particular size of bitmap as an access, even if
* no bitmaps of that size are present. We do not count addition or removal of bitmaps as an
* access.
*/
private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
groupMap 的key的類型是 Key,是通過 Bitmap 的 size 和 config 組合生成的,這也是 SizeConfigStrategy 這個(gè)類名的由來。
GroupedLinkedMap 具體實(shí)現(xiàn)如下圖:
內(nèi)部實(shí)現(xiàn)主要包括3中數(shù)據(jù)結(jié)構(gòu):
- List,用于存儲(chǔ)每個(gè)LinkedEntry中的數(shù)據(jù)
- LinkedEntry,本質(zhì)是循環(huán)雙鏈表,用于記錄數(shù)據(jù)的順序
- HashMap,用于快速找到對(duì)應(yīng)的 Entry
總結(jié)一下,BitmapPool 需要注意的有以下幾點(diǎn):
- BitmapPool 大小通過 MemorySizeCalculator 設(shè)置;
- 使用 LRU 算法維護(hù) BitmapPool ;
- Glide 會(huì)根據(jù) Bitmap 的 size 和 Config 生成一個(gè) Key;
- Key 也有自己對(duì)應(yīng)的對(duì)象池,使用 Queue 實(shí)現(xiàn);
- 數(shù)據(jù)最終存儲(chǔ)在 GroupedLinkedMap 中;
- GroupedLinkedMap 使用哈希表、循環(huán)鏈表、List 來存儲(chǔ)數(shù)據(jù)。
MemoryCache
在最上面的 Glide 緩存層級(jí)中提到過,MemoryCache 中存儲(chǔ)的是最近被加載到內(nèi)存中并且還未被釋放的資源。
MemoryCache 接口的實(shí)現(xiàn)類是 LruResourceCache,和 ActiveResources 的緩存策略一樣,也是采用LRU cache。具體實(shí)現(xiàn)就不贅述了。
磁盤緩存
磁盤緩存同樣采用LRU策略,它的實(shí)現(xiàn)類是 DiskLruCacheWrapper,看名字就知道這是一個(gè)包裝類,包裝的是 DiskLruCache。
DiskLruCache
首先想一個(gè)問題,磁盤緩存同樣采用LRU,那么Glide怎樣在APP啟動(dòng)的時(shí)候知道資源的使用頻率并給資源排序呢?
其實(shí)Glide 的做法是創(chuàng)建一個(gè)日志清單文件來保存這個(gè)順序。DiskLruCache 在 APP 第一次安裝時(shí)會(huì)在緩存文件夾下創(chuàng)建一個(gè) journal 日志文件來記錄圖片的添加、刪除、讀取等等操作,后面每次打開 APP 都會(huì)讀取這個(gè)文件,把其中記錄下來的緩存文件名讀取到 LinkedHashMap 中,后面每次對(duì)圖片的操作不僅是操作這個(gè) LinkedHashMap 還要記錄在 journal 文件中。
DiskLruCache 里有很多IO操作,就是因?yàn)樗斯芾砭彺嬷膺€要把對(duì)資源的操作記錄到日志清單文件中。
磁盤緩存的策略
Glide中定義的磁盤緩存策略有下面五種:
- DiskCacheStrategy.ALL:原始圖片和轉(zhuǎn)換過的圖片都緩存
- DiskCacheStrategy.RESOURCE:只緩存原始圖片
- DiskCacheStrategy.NONE:不緩存
- DiskCacheStrategy.DATA:只緩存使用過的圖片
- DiskCacheStrategy.AUTOMATIC: 默認(rèn)策略,嘗試自動(dòng)為本地和遠(yuǎn)程圖片使用最佳的緩存策略。當(dāng)你加載遠(yuǎn)程數(shù)據(jù)(比如,從URL下載)時(shí),AUTOMATIC 策略僅會(huì)存儲(chǔ)未被你的加載過程修改過(比如,變換,裁剪–譯者注)的原始數(shù)據(jù),因?yàn)橄螺d遠(yuǎn)程數(shù)據(jù)相比調(diào)整磁盤上已經(jīng)存在的數(shù)據(jù)要昂貴得多。對(duì)于本地?cái)?shù)據(jù),AUTOMATIC 策略則會(huì)僅存儲(chǔ)變換過的縮略圖,因?yàn)榧词鼓阈枰俅紊闪硪粋€(gè)尺寸或類型的圖片,取回原始數(shù)據(jù)也很容易。