轉(zhuǎn)載請(qǐng)注明出處:
源碼分析glide中三層存儲(chǔ)機(jī)制并與常規(guī)三層緩存機(jī)制對(duì)比
地址:http://m.itdecent.cn/p/dc8fcf7e69bc
目錄

常規(guī)三層緩存機(jī)制
三級(jí)緩存的流程
強(qiáng)引用->軟引用->硬盤緩存
當(dāng)我們的APP中想要加載某張圖片時(shí),先去LruCache中尋找圖片,如果LruCache中有,則直接取出來(lái)使用,如果LruCache中沒(méi)有,則去SoftReference中尋找(軟引用適合當(dāng)cache,當(dāng)內(nèi)存吃緊的時(shí)候才會(huì)被回收。而weakReference在每次system.gc()就會(huì)被回收)(當(dāng)LruCache存儲(chǔ)緊張時(shí),會(huì)把最近最少使用的數(shù)據(jù)放到SoftReference中),如果SoftReference中有,則從SoftReference中取出圖片使用,同時(shí)將圖片重新放回到LruCache中,如果SoftReference中也沒(méi)有圖片,則去硬盤緩存中中尋找,如果有則取出來(lái)使用,同時(shí)將圖片添加到LruCache中,如果沒(méi)有,則連接網(wǎng)絡(luò)從網(wǎng)上下載圖片。圖片下載完成后,將圖片保存到硬盤緩存中,然后放到LruCache中。(參考這里)
強(qiáng)引用
強(qiáng)引用不能是隨便的一個(gè)map數(shù)組,因?yàn)檫€要處理最近不用的數(shù)據(jù)。一般用的是LruCache(這是內(nèi)存存儲(chǔ),以前一直以為這是disk硬盤存儲(chǔ),囧,那個(gè)叫DiskLruCache),里面持有一個(gè)LinkedHashMap,需要將持有的對(duì)象進(jìn)行按時(shí)間排序,這樣才能實(shí)現(xiàn)Lru算法(Least Recently Used),也就是當(dāng)達(dá)到最大的存儲(chǔ)限制時(shí),把最近最少使用的移除。使用Lrucache需要實(shí)現(xiàn)的最主要的方法(參考這里):
- entryRemoved(boolean evicted, String key, T oldValue, T newValue)這個(gè)作用是,當(dāng)存儲(chǔ)滿了以后,需要移除一個(gè)最近不使用的。也就是這個(gè)oldValue,我們可以把這個(gè)oldValue存到本地或者變成弱引用?;蛘咧苯觨ldValue==null(因?yàn)長(zhǎng)ruCache只是把最近不使用的那個(gè)移出map,并沒(méi)有置為null即沒(méi)有移除內(nèi)存,這個(gè)如果需要的話得自己處理)
- create(String key)這個(gè)就是當(dāng)用戶從內(nèi)存中獲取一個(gè)value的時(shí)候,沒(méi)有找到,可以在這里生成一個(gè),之后會(huì)放cache中。不過(guò)我們一般會(huì)用put進(jìn)行放置,所以這里不實(shí)現(xiàn)也ok。
- int sizeOf(K key, V value)//這個(gè)方法要特別注意,跟我們實(shí)例化 LruCache 的 maxSize 要呼應(yīng),怎么做到呼應(yīng)呢,比如 maxSize 的大小為緩存的個(gè)數(shù),這里就是 return 1就 ok,如果是內(nèi)存的大小,如果5M,這個(gè)就不能是個(gè)數(shù) 了,這是應(yīng)該是每個(gè)緩存 value 的 size 大小,如果是 Bitmap,這應(yīng)該是 bitmap.getByteCount();
軟引用
當(dāng)圖片被LruCache移除的時(shí)候,我們需要手動(dòng)將這張圖片添加到軟引用map(SoftReference)中,也就是在剛才提到的entryRemoved()中做這件事。我們需要在項(xiàng)目中維護(hù)一個(gè)由SoftReference組成的集合,其中存儲(chǔ)被LruCache移除出來(lái)的圖片。軟引用的一個(gè)好處是當(dāng)系統(tǒng)空間緊張的時(shí)候,軟引用可以隨時(shí)銷毀(當(dāng)然前提是這個(gè)內(nèi)存對(duì)象除了軟引用之外沒(méi)有其他引用),因此軟引用是不會(huì)影響系統(tǒng)運(yùn)行的,換句話說(shuō),如果系統(tǒng)因?yàn)槟硞€(gè)原因OOM了,那么這個(gè)原因肯定不是軟引用引起的。例如:
private Map<String, SoftReference<Bitmap>> cacheMap=new HashMap<>();
@Override // 當(dāng)有圖片從LruCache中移除時(shí),將其放進(jìn)軟引用集合中
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null) {
SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);
cacheMap.put(key, softReference);
}
glide中三層緩存機(jī)制
glide作為一個(gè)優(yōu)秀的圖片加載開(kāi)源庫(kù),使用的就是三級(jí)存儲(chǔ)機(jī)制。下面就詳細(xì)說(shuō)下(稍微和一般的三層緩存機(jī)制不一樣):
三層存儲(chǔ)的機(jī)制在Engine中實(shí)現(xiàn)的。先說(shuō)下Engine是什么?Engine這一層負(fù)責(zé)加載時(shí)做管理內(nèi)存緩存的邏輯。持有MemoryCache、Map<Key, WeakReference<EngineResource<?>>>。通過(guò)load()來(lái)加載圖片,加載前后會(huì)做內(nèi)存存儲(chǔ)的邏輯。如果內(nèi)存緩存中沒(méi)有,那么才會(huì)使用EngineJob這一層來(lái)進(jìn)行異步獲取硬盤資源或網(wǎng)絡(luò)資源。EngineJob類似一個(gè)異步線程或observable。Engine是一個(gè)全局唯一的,通過(guò)Glide.getEngine()來(lái)獲取。
那么
在Engine類的load()方法前面有這么一段注釋:
<p>
* The flow for any request is as follows:
* <ul>
* <li>Check the memory cache and provide the cached resource if present</li>
* <li>Check the current set of actively used resources and return the active resource if present</li>
* <li>Check the current set of in progress loads and add the cb to the in progress load if present</li>
* <li>Start a new load</li>
* </ul>
* </p>
*
Active resources are those that have been provided to at least one request and have not yet been released.
* Once all consumers of a resource have released that resource, the resource then goes to cache. If the
* resource is ever returned to a new consumer from cache, it is re-added to the active resources. If the
* resource is evicted from the cache, its resources are recycled and re-used if possible and the resource is
* discarded. There is no strict requirement that consumers release their resources so active resources are
* held weakly.
active resources存在的形式是弱引用:
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
MemoryCache是強(qiáng)引用的內(nèi)存緩存,其實(shí)現(xiàn)類:
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache
說(shuō)到這里,我們具體說(shuō)下Engine類的load()方法:
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
這兩句話是獲取key,獲取key會(huì)使用width,height,transform等,注意這里還會(huì)使用fetcher.getId()。這個(gè)和圖片的url有關(guān),可以自定義,所以可以進(jìn)行動(dòng)態(tài)url的存儲(chǔ)。詳細(xì)可以看這里.
接下來(lái)就是加載內(nèi)存緩存了:
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
@SuppressWarnings("unchecked")
private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);
final EngineResource result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource) cached;
} else {
result = new EngineResource(cached, true /*isCacheable*/);
}
return result;
}
大致獲取緩存圖片的流程是:如果Lruche中有,那么根據(jù)key把對(duì)應(yīng)的EngineResource取出,并從Lruche中刪除。當(dāng)EngineResource從Lruche刪除時(shí),會(huì)馬上回調(diào)一個(gè)onResourceRemoved()接口方法,這個(gè)方法在Engine中實(shí)現(xiàn):
@Override
public void onResourceRemoved(final Resource<?> resource) {
Util.assertMainThread();
resourceRecycler.recycle(resource);
}
在resourceRecycler.recycle(resource);中使用handler將回收的任務(wù)給到了主線程,即在主線程對(duì)資源進(jìn)行回收。當(dāng)然在資源回收時(shí),需要進(jìn)行判斷,看是不是還有其他地方對(duì)資源進(jìn)行了引用,如果有,那么就不進(jìn)行回收;沒(méi)有沒(méi)有,那么再進(jìn)行回收??聪翬ngineResource:
@Override
public void recycle() {
if (acquired > 0) {//是否還有引用
throw new IllegalStateException("Cannot recycle a resource while it is still acquired");
}
if (isRecycled) {
throw new IllegalStateException("Cannot recycle a resource that has already been recycled");
}
isRecycled = true;
resource.recycle();
}
從前面的loadFromCache()代碼可以看出,當(dāng)把資源從Lruche中取出以后,會(huì)執(zhí)行:
if (cached != null) {
cached.acquire();
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
資源引用會(huì)加1(因?yàn)楝F(xiàn)在的場(chǎng)景是獲取的緩存圖片會(huì)被使用,所以資源引用加1),并且把資源放到弱引用map中。所以這種情況下,前面的資源回收并不會(huì)執(zhí)行。如果當(dāng)需求清楚資源時(shí)(比如頁(yè)面關(guān)閉,請(qǐng)求cancle),那么會(huì)調(diào)用EngineResource的release()方法將資源引用減1。如果引用變成0后,會(huì)調(diào)用一個(gè)onResourceReleased()接口方法,其在Engine中實(shí)現(xiàn):
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
會(huì)把資源從activeResources弱引用數(shù)組中清除,然后把資源放到Lruche中,然后將資源進(jìn)行回收。
當(dāng)資源異步加載成功后(網(wǎng)絡(luò)/diskLrucache),除了會(huì)放到diskLrucache中(網(wǎng)絡(luò)請(qǐng)求),資源還會(huì)放到哪里呢?
@SuppressWarnings("unchecked")
@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
// TODO: should this check that the engine job is still current?
jobs.remove(key);
}
可以看到資源是放到activeResources中了。
我們總結(jié)一下:需要一個(gè)圖片資源,假設(shè)Lruche中相應(yīng)的資源圖片,那么就就返回相應(yīng)資源,同時(shí)從Lruche中清除,放到activeResources中。activeResources map是盛放正在使用的資源,以弱引用的形式存在。同時(shí)資源內(nèi)部有被引用的記錄。如果資源沒(méi)有引用記錄了,那么再放回Lruche中,同時(shí)從activeResources中清除。需要一個(gè)圖片資源如果Lruche中沒(méi)有,就從activeResources中找,找到后相應(yīng)資源的引用加1。如果Lruche和activeResources中沒(méi)有,那么進(jìn)行資源異步請(qǐng)求(網(wǎng)絡(luò)/diskLrucache),請(qǐng)求成功后,資源放到diskLrucache和activeResources中。
核心思想就是:使用一個(gè)弱引用map activeResources來(lái)盛放項(xiàng)目中正在使用的資源。Lruche中不含有正在使用的資源。資源內(nèi)部有個(gè)計(jì)數(shù)器來(lái)顯示自己是不是還有被引用的情況(當(dāng)然這里說(shuō)的被項(xiàng)目中使用/引用不包括被Lruche/activeResources引用)。把正在使用的資源和沒(méi)有被使用的資源分開(kāi)有什么好處呢?因?yàn)楫?dāng)Lruche需要移除一個(gè)緩存時(shí),會(huì)調(diào)用resource.recycle()方法。注意到該方法上面注釋寫著只有沒(méi)有任何consumer引用該資源的時(shí)候才可以調(diào)用這個(gè)方法。這也是為什么需要保證Lruche中的緩存資源都不能有外界的引用。那么為什么調(diào)用resource.recycle()方法需要保證該資源沒(méi)有任何consumer引用呢?一開(kāi)始我不是很理解,因?yàn)橄馼itmap.recycle(),即使bitmap還有資源引用,調(diào)用recycle()也沒(méi)關(guān)系啊,大不了就是不會(huì)被gc罷了。后來(lái)發(fā)現(xiàn)glide中resource定義的recycle()和bitmap中的recycle()并不是一回事。glide中resource定義的recycle()要做的事情是把這個(gè)不用的資源(假設(shè)是bitmap或drawable)放到bitmapPool中。我們知道bitmapPool是一個(gè)bitmap回收再利用的庫(kù),在做transform的時(shí)候會(huì)從這個(gè)bitmapPool中拿一個(gè)bitmap進(jìn)行再利用。這樣就避免了重新創(chuàng)建bitmap,減少了內(nèi)存的開(kāi)支。而既然bitmapPool中的bitmap會(huì)被重復(fù)利用,那么肯定要保證回收該資源的時(shí)候(即調(diào)用資源的recycle()時(shí)),要保證該資源真的沒(méi)有外界引用了。這也是為什么glide花費(fèi)那么多邏輯來(lái)保證Lruche中的資源沒(méi)有外界引用的原因。