Glide源碼解析之MemoryCache

前言

在上文Glide源碼解析之ActiveResources我們介紹了一級(jí)緩存ActiveResource,現(xiàn)在讓我們來(lái)看Glide的二級(jí)緩存MemoryCache。

二級(jí)緩存

在load()中首先會(huì)從ActiveResource中獲取EngineResource,如果獲取不到,接下來(lái)才會(huì)從MemoryCache中獲取。在getEngineResourceFromCache()中調(diào)用的MemoryCache的remove(),所以是會(huì)在MemoryCache中刪除這個(gè)Resource,并且在獲取成功后把EngineResource存入ActiveResource中。

在上文我們提到如果設(shè)置了isActiveResourceRetentionAllowed,則當(dāng)ActiveResource中的Resource被回收時(shí)又會(huì)存入MemoryCache中。Glide這樣設(shè)計(jì)可以保證最近活躍的資源可以最大限度的在內(nèi)存中緩存。

    //Engine.load()
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
        return null;
    }
        

    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        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, true /*isMemoryCacheable*/, true /*isRecyclable*/);
        }
        return result;
    }

MemoryCache

MemoryCache是一個(gè)接口,在GlideBuilder的中會(huì)對(duì)MemoryCache進(jìn)行初始化,它的實(shí)現(xiàn)類是LruResourceCache。

    //GlideBuilder
    if (memoryCache == null) {
        memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }

LruResourceCache

LruResourceCache繼承了LruCache,但是使用的不是系統(tǒng)提供的LruCache,而是Glide自己實(shí)現(xiàn)的,并且實(shí)現(xiàn)了MemoryCache接口。

    public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
    }

LruCache

和系統(tǒng)的LruCache一樣都是使用了LinkedHashMap來(lái)實(shí)現(xiàn)最近最少使用原則,并且存放的元素總大小不能大于設(shè)定的大小。

    public class LruCache<T, Y> {
        //這是一個(gè)繼承了HashMap,但里面的Entry是使用LinkedHashMapEntry來(lái)作為一個(gè)雙鏈表,這樣既使用了HashMap的存取速度大多數(shù)情況下為O(1),又保證了順序。
        //第三個(gè)參數(shù)為true代表鏈表按照訪問(wèn)順序存放,最近訪問(wèn)(存取)的放在鏈表的尾部。
        private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
        private final long initialMaxSize;      //初始化時(shí)最大的大小
        private long maxSize;                   //最大的大小
        private long currentSize;               //當(dāng)前LinkedHashMap保存的元素的總大小
    }
put()

首先判斷新元素的是否大小是否比總大小大,大的話則調(diào)用onItemEvicted()通知這個(gè)元素被刪除了,不存進(jìn)去。

接著加上計(jì)算新元素的大小,并存進(jìn)去,如果同一個(gè)key有舊元素的話則還要減去舊元素的大小,如果新舊不是同一個(gè)元素則也是調(diào)用onItemEvicted()通知舊元素被刪除了。最后再調(diào)用evict()來(lái)看當(dāng)前大小是否超出總大小,是的話則刪除最近最少使用的元素(鏈表頭)。

    public synchronized Y put(@NonNull T key, @Nullable Y item) {
        final int itemSize = getSize(item);
        if (itemSize >= maxSize) {
            onItemEvicted(key, item);
            return null;
        }

        if (item != null) {
            currentSize += itemSize;
        }
        @Nullable final Y old = cache.put(key, item);
        if (old != null) {
            currentSize -= getSize(old);

            if (!old.equals(item)) {
                onItemEvicted(key, old);
            }
        }
        evict();

        return old;
    }
onItemEvicted()

這個(gè)方法主要就是當(dāng)元素被刪除時(shí)進(jìn)行通知的,由子類LruResourceCache進(jìn)行了重寫(xiě),最后會(huì)回調(diào)給Engine,對(duì)resource進(jìn)行回收處理。

    protected void onItemEvicted(@NonNull T key, @Nullable Y item) {
        // optional override
    }
    
    //LruResourceCache
    protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
        if (listener != null && item != null) {
            listener.onResourceRemoved(item);
        }
    }
    
    //Engine
    @Override
    public void onResourceRemoved(@NonNull final Resource<?> resource) {
        resourceRecycler.recycle(resource);
    }
evict()

這個(gè)方法是對(duì)當(dāng)前元素的總大小進(jìn)行處理,如果超過(guò)了maxSize,則會(huì)一直循環(huán)刪掉鏈表的第一個(gè)元素。因?yàn)樽罱L問(wèn)的元素是放在鏈表的最后的,所以這樣實(shí)現(xiàn)了最近最少使用的元素會(huì)被刪除。

    private void evict() {
        trimToSize(maxSize);
    }
    
    protected synchronized void trimToSize(long size) {
        Map.Entry<T, Y> last;
        Iterator<Map.Entry<T, Y>> cacheIterator;
        while (currentSize > size) {
            cacheIterator = cache.entrySet().iterator();
            last = cacheIterator.next();
            final Y toRemove = last.getValue();
            currentSize -= getSize(toRemove);
            final T key = last.getKey();
            cacheIterator.remove();
            onItemEvicted(key, toRemove);
        }
    }
remove()

根據(jù)key從LinkedHashMap中獲取value,如果不為空則當(dāng)前大小減去value的大小。

    public synchronized Y remove(@NonNull T key) {
        final Y value = cache.remove(key);
        if (value != null) {
            currentSize -= getSize(value);
        }
        return value;
    }
getSize()

返回Resource的大小,如果是BitmapResource則返回的是Bitmap占用的內(nèi)存大小。

    protected int getSize(@Nullable Resource<?> item) {
        if (item == null) {
            return super.getSize(null);
        } else {
            return item.getSize();
        }
    }
    
    //LruResouceCache
    @Override
    protected int getSize(@Nullable Resource<?> item) {
        if (item == null) {
            return super.getSize(null);
        } else {
            return item.getSize();
        }
    }
    
    //BitmapResouce
    @Override
    public int getSize() {
        return Util.getBitmapByteSize(bitmap);
    }
    
    /**
     * 返回Bitmap占用的內(nèi)存
     */
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static int getBitmapByteSize(@NonNull Bitmap bitmap) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
          try {
            return bitmap.getAllocationByteCount();
          } catch (@SuppressWarnings("PMD.AvoidCatchingNPE") NullPointerException e) {
            // Do nothing.
          }
        }
        return bitmap.getHeight() * bitmap.getRowBytes();
    }
trimMemory()

我們知道當(dāng)系統(tǒng)內(nèi)存緊張的時(shí)候會(huì)回調(diào)給ComponentCallbacks2,一般我們會(huì)在相應(yīng)的回調(diào)里清理內(nèi)存,防止被系統(tǒng)殺掉,而Glide同樣幫我們做了釋放內(nèi)存的操作。

    //LruResouceCache
    public void trimMemory(int level) {
        if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // 應(yīng)用已經(jīng)進(jìn)入系統(tǒng)的LRU應(yīng)用列表了
            clearMemory();
        } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
                || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
            // 應(yīng)用沒(méi)有UI正在顯示,或者是一個(gè)前臺(tái)應(yīng)用,但是當(dāng)前系統(tǒng)的內(nèi)存很緊張,無(wú)法保持后臺(tái)進(jìn)程的運(yùn)行。
            trimToSize(getMaxSize() / 2);   //清除到只剩一半內(nèi)存占用
        }
    }
    
    //LruCache
    public void clearMemory() {
        trimToSize(0);      //清除全部?jī)?nèi)存緩存
    }
    
    //Glide
    public void trimMemory(int level) {
        memoryCache.trimMemory(level);
        bitmapPool.trimMemory(level);
        arrayPool.trimMemory(level);
    }
    
    //ComponentCallbacks2
    @Override
    public void onTrimMemory(int level) {
        trimMemory(level);
    }
    
    //初始化完Glide后就注冊(cè)ComponentCallbacks2
    applicationContext.registerComponentCallbacks(glide);

MemoryCache最大占用內(nèi)存的大小

在GlideBuilder對(duì)MemoryCache初始化的時(shí)候使用的是memorySizeCalculator.getMemoryCacheSize(),那么接下來(lái)看一下MemorySizeCalculator是怎樣計(jì)算MemoryCacheSize的。

下面注釋寫(xiě)的聽(tīng)清楚,應(yīng)該能看懂,一般來(lái)說(shuō)最終MemoryCacheSize的大小就是兩張占滿屏幕的圖片的大小,以1080 X 1920的圖片來(lái)說(shuō)就是1080 X 1920 X 4 ≈ 8M。


    //GlideBuilder
    if (memoryCache == null) {
        memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
    
    MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
        this.context = builder.context;

        //默認(rèn)為 4MB,如果是低內(nèi)存設(shè)備(運(yùn)存少于1GB,或者系統(tǒng)在4.4以下)則在此基礎(chǔ)上除以二
        arrayPoolSize =
                isLowMemoryDevice(builder.activityManager)
                        ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
                        : builder.arrayPoolSizeBytes;
                        
        //先獲取當(dāng)前進(jìn)程可使用內(nèi)存大小,
        //然后判斷是否為低內(nèi)存設(shè)備再乘以相應(yīng)的系數(shù),
        //普通設(shè)備是乘以 0.4,低內(nèi)存為 0.33,這樣得到的是 Glide 可使用的最大內(nèi)存大小
        int maxSize =getMaxSize(
                        builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);

        int widthPixels = builder.screenDimensions.getWidthPixels();
        int heightPixels = builder.screenDimensions.getHeightPixels();
        
        //計(jì)算一張格式為ARGB_8888,大小為屏幕大小的圖片的占用內(nèi)存大小
        //BYTES_PER_ARGB_8888_PIXEL值為 4
        int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;

        int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);

        //memoryCacheScreens值為2
        int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
        
        //去掉 ArrayPool 占用的內(nèi)存后還剩余的內(nèi)存
        int availableSize = maxSize - arrayPoolSize;

        if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
            //未超出可用內(nèi)存限制
            memoryCacheSize = targetMemoryCacheSize;
            bitmapPoolSize = targetBitmapPoolSize;
        } else {
            //超出限制,則按比例縮小
            float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
            memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
            bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
        }

    }

總結(jié)

作為Glide的二級(jí)緩存,MemoryCache使用LruCache來(lái)實(shí)現(xiàn)最近最少使用的原則,從而使Resource不會(huì)一直占用內(nèi)存,并且會(huì)在內(nèi)存緊張的時(shí)候自動(dòng)幫我們回收內(nèi)存,可以說(shuō)是考慮的很周到了。

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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