源碼分析glide中三層存儲(chǔ)機(jī)制并與常規(guī)三層緩存機(jī)制對(duì)比

轉(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)有外界引用的原因。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容