Glide源码解析之MemoryCache

前言

在上文Glide源码解析之ActiveResources我们介绍了一级缓存ActiveResource,现在让我们来看Glide的二级缓存MemoryCache。

二级缓存

在load()中首先会从ActiveResource中获取EngineResource,如果获取不到,接下来才会从MemoryCache中获取。在getEngineResourceFromCache()中调用的MemoryCache的remove(),所以是会在MemoryCache中删除这个Resource,并且在获取成功后把EngineResource存入ActiveResource中。

在上文我们提到如果设置了isActiveResourceRetentionAllowed,则当ActiveResource中的Resource被回收时又会存入MemoryCache中。Glide这样设计可以保证最近活跃的资源可以最大限度的在内存中缓存。

    //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是一个接口,在GlideBuilder的中会对MemoryCache进行初始化,它的实现类是LruResourceCache。

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

LruResourceCache

LruResourceCache继承了LruCache,但是使用的不是系统提供的LruCache,而是Glide自己实现的,并且实现了MemoryCache接口。

    public class LruResourceCache extends LruCache> implements MemoryCache {
    }

LruCache

和系统的LruCache一样都是使用了LinkedHashMap来实现最近最少使用原则,并且存放的元素总大小不能大于设定的大小。

    public class LruCache {
        //这是一个继承了HashMap,但里面的Entry是使用LinkedHashMapEntry来作为一个双链表,这样既使用了HashMap的存取速度大多数情况下为O(1),又保证了顺序。
        //第三个参数为true代表链表按照访问顺序存放,最近访问(存取)的放在链表的尾部。
        private final Map cache = new LinkedHashMap<>(100, 0.75f, true);
        private final long initialMaxSize;      //初始化时最大的大小
        private long maxSize;                   //最大的大小
        private long currentSize;               //当前LinkedHashMap保存的元素的总大小
    }
put()

首先判断新元素的是否大小是否比总大小大,大的话则调用onItemEvicted()通知这个元素被删除了,不存进去。

接着加上计算新元素的大小,并存进去,如果同一个key有旧元素的话则还要减去旧元素的大小,如果新旧不是同一个元素则也是调用onItemEvicted()通知旧元素被删除了。最后再调用evict()来看当前大小是否超出总大小,是的话则删除最近最少使用的元素(链表头)。

    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()

这个方法主要就是当元素被删除时进行通知的,由子类LruResourceCache进行了重写,最后会回调给Engine,对resource进行回收处理。

    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()

这个方法是对当前元素的总大小进行处理,如果超过了maxSize,则会一直循环删掉链表的第一个元素。因为最近访问的元素是放在链表的最后的,所以这样实现了最近最少使用的元素会被删除。

    private void evict() {
        trimToSize(maxSize);
    }
    
    protected synchronized void trimToSize(long size) {
        Map.Entry last;
        Iterator> 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()

根据key从LinkedHashMap中获取value,如果不为空则当前大小减去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占用的内存大小。

    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占用的内存
     */
    @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()

我们知道当系统内存紧张的时候会回调给ComponentCallbacks2,一般我们会在相应的回调里清理内存,防止被系统杀掉,而Glide同样帮我们做了释放内存的操作。

    //LruResouceCache
    public void trimMemory(int level) {
        if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // 应用已经进入系统的LRU应用列表了
            clearMemory();
        } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
                || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
            // 应用没有UI正在显示,或者是一个前台应用,但是当前系统的内存很紧张,无法保持后台进程的运行。
            trimToSize(getMaxSize() / 2);   //清除到只剩一半内存占用
        }
    }
    
    //LruCache
    public void clearMemory() {
        trimToSize(0);      //清除全部内存缓存
    }
    
    //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后就注册ComponentCallbacks2
    applicationContext.registerComponentCallbacks(glide);

MemoryCache最大占用内存的大小

在GlideBuilder对MemoryCache初始化的时候使用的是memorySizeCalculator.getMemoryCacheSize(),那么接下来看一下MemorySizeCalculator是怎样计算MemoryCacheSize的。

下面注释写的听清楚,应该能看懂,一般来说最终MemoryCacheSize的大小就是两张占满屏幕的图片的大小,以1080 X 1920的图片来说就是1080 X 1920 X 4 ≈ 8M。


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

        //默认为 4MB,如果是低内存设备(运存少于1GB,或者系统在4.4以下)则在此基础上除以二
        arrayPoolSize =
                isLowMemoryDevice(builder.activityManager)
                        ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
                        : builder.arrayPoolSizeBytes;
                        
        //先获取当前进程可使用内存大小,
        //然后判断是否为低内存设备再乘以相应的系数,
        //普通设备是乘以 0.4,低内存为 0.33,这样得到的是 Glide 可使用的最大内存大小
        int maxSize =getMaxSize(
                        builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);

        int widthPixels = builder.screenDimensions.getWidthPixels();
        int heightPixels = builder.screenDimensions.getHeightPixels();
        
        //计算一张格式为ARGB_8888,大小为屏幕大小的图片的占用内存大小
        //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 占用的内存后还剩余的内存
        int availableSize = maxSize - arrayPoolSize;

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

    }

总结

作为Glide的二级缓存,MemoryCache使用LruCache来实现最近最少使用的原则,从而使Resource不会一直占用内存,并且会在内存紧张的时候自动帮我们回收内存,可以说是考虑的很周到了。

你可能感兴趣的:(Glide源码解析之MemoryCache)