前言
在上文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不会一直占用内存,并且会在内存紧张的时候自动帮我们回收内存,可以说是考虑的很周到了。