系列文章:
- Glide源碼探究(一) - 生命周期綁定與Request創(chuàng)建
- Glide源碼探究(二) - 內(nèi)存緩存
- Glide源碼探究(三) - 網(wǎng)絡(luò)資源加載
- Glide源碼探究(四) - Bitmap復(fù)用機(jī)制
讓我們接著上一篇筆記繼續(xù)講Engine的load方法,這里面就是Glide的資源加載流程。
public <R> LoadStatus load(...) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
EngineResource<?> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
return waitForExistingOrStartNewJob(...);
}
}
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE, false);
return null;
}
這個(gè)方法的流程其實(shí)也挺清晰的:
- 創(chuàng)建緩存的key,這個(gè)key由一系列的參數(shù)組成,其中最重要的參數(shù)model在我們的例子中就是傳進(jìn)去的url。
- 使用這個(gè)key從內(nèi)存緩存中查詢資源
- 如果內(nèi)存緩存中查不到資源就開啟線程去加載資源
- 如果內(nèi)存緩存中可以查到資源就調(diào)用cb.onResourceReady回調(diào)
流程圖如下:

內(nèi)存緩存
內(nèi)存緩存的流程也比較清晰從代碼上看,如果開啟了內(nèi)存緩存的話會(huì)先從ActiveResources中查詢,查不到的話再?gòu)腃ache里面查詢:
private EngineResource<?> loadFromMemory( EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
...
return active;
}
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
...
return cached;
}
return null;
}
這兩個(gè)東西同樣是內(nèi)存緩存,那有啥區(qū)別呢?我們先看ActiveResources:
// Engine
private EngineResource<?> loadFromActiveResources(Key key) {
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
// ActiveResources
final class ActiveResources {
...
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
...
synchronized EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
...
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
...
}
...
}
loadFromActiveResources實(shí)際上是從弱引用緩存里面查詢資源。既然是緩存當(dāng)然就要講講它的添加和刪除。
弱引用緩存的添加
首先是添加,弱引用緩存的添加基本有兩個(gè)時(shí)機(jī)。
- 從Cache里面查詢到的時(shí)候如果能查到,會(huì)將查到的資源放入弱引用緩存:
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
- 子線程加載完資源后會(huì)將資源放入弱引用緩存:
public synchronized void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
if (resource != null && resource.isMemoryCacheable()) {
activeResources.activate(key, resource);
}
...
}
弱引用緩存的刪除
細(xì)心的同學(xué)可能會(huì)看到cached.acquire()這個(gè)操作,我們來看看它的代碼:
synchronized void acquire() {
...
++acquired;
}
有沒有想到些啥?沒錯(cuò),引用計(jì)數(shù)!
EngineResource是通過引用計(jì)數(shù)來管理的。有引用計(jì)數(shù)增加那就有引用計(jì)數(shù)減少。減少的操作在release方法里面:
void release() {
boolean release = false;
synchronized (this) {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (--acquired == 0) {
release = true;
}
}
if (release) {
listener.onResourceReleased(key, this);
}
}
如果引用計(jì)數(shù)降到了0就會(huì)調(diào)用listener的onResourceReleased回調(diào)回去,在onResourceReleased里面Engine會(huì)將資源從弱引用緩存刪除然后移到cache里:
// Engine
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
}
}
// ActiveResources
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
EngineResource.release又是什么時(shí)候被調(diào)用的呢?其實(shí)調(diào)用的地方有好幾處,但是最重要的兩處是
- 我們手動(dòng)調(diào)Glide的clear清理資源的時(shí)候:
// 手動(dòng)清理資源
Glide.with(context)
.clear(img)
- 綁定的生命LifecycleListener.onDestroy的時(shí)候:
// RequestManager
public synchronized void onDestroy() {
...
for (Target<?> target : targetTracker.getAll()) {
clear(target);
}
...
}
public void clear(@Nullable final Target<?> target) {
...
untrackOrDelegate(target);
}
private void untrackOrDelegate(@NonNull Target<?> target) {
...
Request request = target.getRequest();
if (!isOwnedByUs && !glide.removeFromManagers(target) && request != null) {
target.setRequest(null);
request.clear();
}
}
// SingleRequest
public void clear() {
...
engine.release(toRelease);
...
}
// Engine
public void release(Resource<?> resource) {
if (resource instanceof EngineResource) {
((EngineResource<?>) resource).release();
}
...
}
簡(jiǎn)單來講就是加載資源的時(shí)候會(huì)把資源放入弱引用緩存,但資源不需要的時(shí)候會(huì)從弱引用緩存里面拿出移到另一個(gè)內(nèi)存緩存里面。所以這些資源都是正在使用的,這個(gè)弱引用緩存Glide把它叫做ActiveResources也是比較準(zhǔn)確的。
這個(gè)緩存使用弱引用的意義在于: 資源是保存在request里面的,而根據(jù)我們上篇筆記的知識(shí),request是以setTag的方式保存在view里面的。所以當(dāng)view被回收之后,resource也就沒有別的強(qiáng)引用可以連接到gc root,可以被java垃圾回收機(jī)制回收
LRUCache
Engine.load會(huì)先從ActiveResources中查詢,查不到的話再?gòu)腃ache里面查詢,這個(gè)Cache其實(shí)是一個(gè)LruResourceCache:
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
...
}
從這個(gè)lru cache里面加載資源意味著把資源從lru cache里面移出,放到弱引用緩存中:
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);
final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
而正如上節(jié)我們講的資源的引用計(jì)數(shù)被清零的時(shí)候就會(huì)從弱引用緩存中刪除,加入lru cache中(注意這里放的是強(qiáng)引用,因?yàn)槭菑膙iew里面getTage拿到Resource強(qiáng)引用進(jìn)行release的):
// Engine
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
}
}
// ActiveResources
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
內(nèi)存緩存整體流程
至此整個(gè)內(nèi)存緩存的架構(gòu)就大體完整了,當(dāng)資源被使用的時(shí)候會(huì)被放到弱引用緩存,當(dāng)資源不再被使用的時(shí)候就會(huì)被放入LRU Cache:

補(bǔ)充: 內(nèi)存緩存的查詢順序
先查弱引用緩存再查lru cache這個(gè)順序并不是一開始就這樣的,我剛看glide源碼的時(shí)候看的是比較舊的版本,那個(gè)時(shí)候是先查lru cahe,查不到再查弱引用緩存。
這個(gè)順序在2017年11月這個(gè)commit之后修改的:

這個(gè)修改是為了修復(fù)資源被重復(fù)加載的bug,但實(shí)際上我看這部分修改的時(shí)候,感覺交換查詢順序應(yīng)該和解這個(gè)bug沒有直接關(guān)系,它更像是作者在重構(gòu)之后覺得先查lru cache再查弱引用緩存的順序怪怪的(我那個(gè)時(shí)候也有這種感覺),順手改了下:

這里它將原本寫在Engine的弱引用Map封裝成了ActiveResources。
那為啥順序不是一開始就是先查弱引用緩存呢?原因可能是一開始的代碼內(nèi)存緩存就沒有弱引用緩存:
public <T, Z> LoadStatus load(String id, int width, int height, ResourceDecoder<InputStream, Z> cacheDecoder,
ResourceFetcher<T> fetcher, ResourceDecoder<T, Z> decoder, Transformation<Z> transformation,
ResourceEncoder<Z> encoder, Priority priority, ResourceCallback cb) {
Key key = keyFactory.buildKey(id, width, height, cacheDecoder, decoder, transformation, encoder);
Resource cached = cache.get(key);
if (cached != null) {
cached.acquire(1);
cb.onResourceReady(cached);
return null;
}
ResourceRunner current = runners.get(key);
if (current != null) {
EngineJob job = current.getJob();
job.addCallback(cb);
return new LoadStatus(cb, job);
}
ResourceRunner<Z> runner = factory.build(key, width, height, cacheDecoder, fetcher, decoder, transformation,
encoder, priority, this);
runner.getJob().addCallback(cb);
runners.put(key, runner);
runner.queue();
return new LoadStatus(cb, runner.getJob());
}
可能是作者在后面優(yōu)化添加這個(gè)弱引用緩存的時(shí)候就順手放到了原有邏輯的后面。
其實(shí)仔細(xì)想想內(nèi)存緩存的架構(gòu),我也覺得這個(gè)順序其實(shí)并不重要,誰先誰后都不會(huì)有什么問題,無非是說流程是從lru cache拿出來放到弱引用緩存的,查詢的時(shí)候先查弱引用緩存會(huì)比較符合一般人的思路。
我們回想下兩個(gè)緩存存放的資源,簡(jiǎn)化到Activity的場(chǎng)景。弱引用緩存放的都是當(dāng)前activity正在使用的圖片,lru cache是activity退出之后回收的圖片。如果先查弱引用緩存,意味著當(dāng)上下不?;瑒?dòng)recyclerview的時(shí)候效率高一丟丟。如果先查lru cahe,意味著反復(fù)進(jìn)出同一個(gè)activity的時(shí)候效率高一丟丟。很難說哪個(gè)順序性能比較高。而且這一丟丟性能在現(xiàn)代設(shè)備中的確真的是毫無影響,所以讓人好理解是最重要的,先查弱引用緩存沒毛病。
這篇的篇幅也差不多了,下一篇我們繼續(xù)講資源的加載部分