轉載請注明出處:
源碼分析glide中三層存儲機制并與正常三層緩存機制對比
位址:http://blog.csdn.net/qq_22744433/article/details/78412786
目錄
正常三層緩存機制
三級緩存的流程
強引用->軟引用->硬碟緩存
當我們的APP中想要加載某張圖檔時,先去LruCache中尋找圖檔,如果LruCache中有,則直接取出來使用,如果LruCache中沒有,則去SoftReference中尋找(軟引用适合當cache,當記憶體吃緊的時候才會被回收。而weakReference在每次system.gc()就會被回收)(當LruCache存儲緊張時,會把最近最少使用的資料放到SoftReference中),如果SoftReference中有,則從SoftReference中取出圖檔使用,同時将圖檔重新放回到LruCache中,如果SoftReference中也沒有圖檔,則去硬碟緩存中中尋找,如果有則取出來使用,同時将圖檔添加到LruCache中,如果沒有,則連接配接網絡從網上下載下傳圖檔。圖檔下載下傳完成後,将圖檔儲存到硬碟緩存中,然後放到LruCache中。(參考這裡)
強引用
強引用不能是随便的一個map數組,因為還要處理最近不用的資料。一般用的是LruCache(這是記憶體存儲,以前一直以為這是disk硬碟存儲,囧,那個叫DiskLruCache),裡面持有一個LinkedHashMap,需要将持有的對象進行按時間排序,這樣才能實作Lru算法(Least Recently Used),也就是當達到最大的存儲限制時,把最近最少使用的移除。使用Lrucache需要實作的最主要的方法(參考這裡):
- entryRemoved(boolean evicted, String key, T oldValue, T newValue)這個作用是,當存儲滿了以後,需要移除一個最近不使用的。也就是這個oldValue,我們可以把這個oldValue存到本地或者變成弱引用。或者直接oldValue==null(因為LruCache隻是把最近不使用的那個移出map,并沒有置為null即沒有移除記憶體,這個如果需要的話得自己處理)
- create(String key)這個就是當使用者從記憶體中擷取一個value的時候,沒有找到,可以在這裡生成一個,之後會放cache中。不過我們一般會用put進行放置,是以這裡不實作也ok。
- int sizeOf(K key, V value)//這個方法要特别注意,跟我們執行個體化 LruCache 的 maxSize 要呼應,怎麼做到呼應呢,比如 maxSize 的大小為緩存的個數,這裡就是 return 1就 ok,如果是記憶體的大小,如果5M,這個就不能是個數 了,這是應該是每個緩存 value 的 size 大小,如果是 Bitmap,這應該是 bitmap.getByteCount();
軟引用
當圖檔被LruCache移除的時候,我們需要手動将這張圖檔添加到軟引用map(SoftReference)中,也就是在剛才提到的entryRemoved()中做這件事。我們需要在項目中維護一個由SoftReference組成的集合,其中存儲被LruCache移除出來的圖檔。軟引用的一個好處是當系統空間緊張的時候,軟引用可以随時銷毀(當然前提是這個記憶體對象除了軟引用之外沒有其他引用),是以軟引用是不會影響系統運作的,換句話說,如果系統因為某個原因OOM了,那麼這個原因肯定不是軟引用引起的。例如:
private Map<String, SoftReference<Bitmap>> cacheMap=new HashMap<>();
@Override // 當有圖檔從LruCache中移除時,将其放進軟引用集合中
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中三層緩存機制
glide作為一個優秀的圖檔加載開源庫,使用的就是三級存儲機制。下面就詳細說下(稍微和一般的三層緩存機制不一樣):
三層存儲的機制在Engine中實作的。先說下Engine是什麼?Engine這一層負責加載時做管理記憶體緩存的邏輯。持有MemoryCache、Map[Key, WeakReference]。通過load()來加載圖檔,加載前後會做記憶體存儲的邏輯。如果記憶體緩存中沒有,那麼才會使用EngineJob這一層來進行異步擷取硬碟資源或網絡資源。EngineJob類似一個異步線程或observable。Engine是一個全局唯一的,通過Glide.getEngine()來擷取。
那麼
在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是強引用的記憶體緩存,其實作類:
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache
說到這裡,我們具體說下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會使用width,height,transform等,注意這裡還會使用fetcher.getId()。這個和圖檔的url有關,可以自定義,是以可以進行動态url的存儲。詳細可以看這裡.
接下來就是加載記憶體緩存了:
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中有,那麼根據key把對應的EngineResource取出,并從Lruche中删除。當EngineResource從Lruche删除時,會馬上回調一個onResourceRemoved()接口方法,這個方法在Engine中實作:
@Override
public void onResourceRemoved(final Resource<?> resource) {
Util.assertMainThread();
resourceRecycler.recycle(resource);
}
在resourceRecycler.recycle(resource);中使用handler将回收的任務給到了主線程,即在主線程對資源進行回收。當然在資源回收時,需要進行判斷,看是不是還有其他地方對資源進行了引用,如果有,那麼就不進行回收;沒有沒有,那麼再進行回收。看下EngineResource:
@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()代碼可以看出,當把資源從Lruche中取出以後,會執行:
if (cached != null) {
cached.acquire();
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
資源引用會加1(因為現在的場景是擷取的緩存圖檔會被使用,是以資源引用加1),并且把資源放到弱引用map中。是以這種情況下,前面的資源回收并不會執行。如果當需求清楚資源時(比如頁面關閉,請求cancle),那麼會調用EngineResource的release()方法将資源引用減1。如果引用變成0後,會調用一個onResourceReleased()接口方法,其在Engine中實作:
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
會把資源從activeResources弱引用數組中清除,然後把資源放到Lruche中,然後将資源進行回收。
當資源異步加載成功後(網絡/diskLrucache),除了會放到diskLrucache中(網絡請求),資源還會放到哪裡呢?
@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中了。
我們總結一下:需要一個圖檔資源,假設Lruche中相應的資源圖檔,那麼就就傳回相應資源,同時從Lruche中清除,放到activeResources中。activeResources map是盛放正在使用的資源,以弱引用的形式存在。同時資源内部有被引用的記錄。如果資源沒有引用記錄了,那麼再放回Lruche中,同時從activeResources中清除。需要一個圖檔資源如果Lruche中沒有,就從activeResources中找,找到後相應資源的引用加1。如果Lruche和activeResources中沒有,那麼進行資源異步請求(網絡/diskLrucache),請求成功後,資源放到diskLrucache和activeResources中。
核心思想就是:使用一個弱引用map activeResources來盛放項目中正在使用的資源。Lruche中不含有正在使用的資源。資源内部有個計數器來顯示自己是不是還有被引用的情況(當然這裡說的被項目中使用/引用不包括被Lruche/activeResources引用)。把正在使用的資源和沒有被使用的資源分開有什麼好處呢?假如突然某一個時刻,我想清空記憶體的Lruche,我直接就可以清空它,因為Lruche中的資源都沒有被項目中使用,這時候我直接讓Lruche所有資源置為null,那麼所有資源就被清空了,可以安全的執行bitmap.recycle()。而正常的三層緩存機制中的Lruche有可能有些資源還被項目中使用,這時候不能直接把bitmap.recycle()。項目中還有對某些資源的引用,是以即使Lruche中的資源置為null。資源也不會清空。activeResources為什麼使用弱引用。首先不用擔心正在被項目使用的資源因為gc而被回收。引用項目中使用/引用了(是強引用),那麼gc時是不會回收這些資源的。隻有所有強引用都不存在了且隻有弱引用,那麼才會gc時回收。是以這裡activeResources使用弱引用是因為有些資源沒有被項目使用了但是也沒有被重新放到Lruche中,就會在gc時回收,不過這樣的資源回收是少數場景。資源回收的大部分發生場景還是根據Lruche的Lru算法(最近最少使用)來清除相應資源。