hello:大家好我是 小小小小小鹿,一枚菜鸡Android程序猿。最近正在阅读Glide源码,今天我们要研究的部分是Glide 内存缓存。
我们知道一般而言图片加载框架会有三级缓存来进行图片加载。内存缓存 文件缓存 网络。在Glide DecodeJob 的工作过程我们知道Glide将文件缓存分成了资源缓存和原始数据缓存。今天我们来研究下Glide的内存缓存相关的内容。
今天的源码阅读有下面几个目标
- 了解活动资源缓存
- 了解LRU缓存
- 为什么会有活动资源缓存这个东西。
Glide图片的请发始于Engine#load方法它的加载流程如下
- 从活动资源中获取图片
- 如果活动资源当中没有图片,从内存缓存中获取
- 内存缓存中没有图片,调用EngineJob DecodeJob 从文件或者网络获取图片。
这里我们只关心如何从内存当中获取图片
//构建缓存的Key
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//从活动资源当中获取
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
//从Lru缓存当中获取图片
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
//后续从文件或者网络当中获取图片
复制代码
活动资源缓存
可能初看到这个名字的小伙伴有点懵,什么是活动资源缓存?我的理解是当前没有被销毁的页面所使用的图片。活动资源缓存涉及到两个关键类EngineResource和ActiveResources。EngineResource作为被缓存的对象,ActiveResources缓存活动资源的集合。
EngineResource
EngineResource是一个包装类,通过它可以对包装的 Resource 接口进行引用计数。每当引用增加的时候通过acquire()对计数器加一,引用减少时通过release()对计数器减一。当计数器为0的时候对资源进行回收。
EngineResource创建过程
EngineResource正常创建有两个位置。
- 当从文件或者网路加载成功的时候会通过EngineResourceFactory#build来创建对应的活动资源。
- 当图片从Lru缓存中加载后,会因为需要将对应的资源放进ActiveResources而进行创建
//toWrap 需要进行引用计数计算的资源
//isCacheable 是否能够被缓存
//isRecyclable 是否已经被回收
EngineResource(Resource<Z> toWrap, boolean isCacheable, boolean isRecyclable) {
resource = Preconditions.checkNotNull(toWrap);
this.isCacheable = isCacheable;
this.isRecyclable = isRecyclable;
}
复制代码
EngineResource引用计数的加减
//有使用的位置 进行加1
synchronized void acquire() {
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
++acquired;
}
@SuppressWarnings("SynchronizeOnNonFinalField")
void release() {
synchronized (listener) {
synchronized (this) {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
//有放弃使用的位置,减一 同时判断如果当前使用者的数量为0 那么将对应的活动资源进行回收
if (--acquired == 0) {
//listener 的实际对象是Engine
listener.onResourceReleased(key, this);
}
}
}
}
复制代码
ActiveResources
ActiveResources内部通过一个Map弱引用的方式对EngineResource进行持有,从而避免内存泄漏。
//ResourceWeakReference是ActiveResources的一个内部类,继承了WeakReference
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
复制代码
ActiveResources的添加,删除,获取
synchronized void activate(Key key, EngineResource<?> resource) {
//构建一个新的存储对象
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
@Nullable
synchronized EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
//如果弱引用持有的资源被回收,将对应的资源清除干净
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
复制代码
内存缓存
当请求被取消或者Request接收到生命周期调用onDestroy页面销毁的时候会通知Request请求释放活动资源
//SingleRequst#clear
public synchronized void clear() {
...
// 释放对应的活动资源
if (resource != null) {
releaseResource(resource);
}
...
}
复制代码
releaseResource会通过Engine#release调用EngineResource#release 如果此时活动计数器的计数为0的话会设置的监听回调到Engine#onResourceReleased
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
//从活动资源中移除对应的资源
activeResources.deactivate(cacheKey);
//如果对应的resource 能够被缓存将其加入缓存中。
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
复制代码
MemoryCache的实现
上面代码的cache是一个MemoryCache接口,在默认不指定的情况下实现的是LruResourceCache,
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
private ResourceRemovedListener listener;
/**
* Constructor for LruResourceCache.
*
* @param size The maximum size in bytes the in memory cache can use.
*/
public LruResourceCache(long size) {
super(size);
}
@Override
public void setResourceRemovedListener(@NonNull ResourceRemovedListener listener) {
this.listener = listener;
}
@Override
protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
//监听元素缓存资源被移除
if (listener != null && item != null) {
listener.onResourceRemoved(item);
}
}
@Override
protected int getSize(@Nullable Resource<?> item) {
if (item == null) {
return super.getSize(null);
} else {
return item.getSize();
}
}
@SuppressLint("InlinedApi")
@Override
public void trimMemory(int level) {
//根据系统对于进程的缓存级别,来决定是全部回收内存缓存还是保留部分
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Entering list of cached background apps
// Evict our entire bitmap cache
clearMemory();
} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
|| level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
// The app's UI is no longer visible, or app is in the foreground but system is running
// critically low on memory
// Evict oldest half of our bitmap cache
trimToSize(getMaxSize() / 2);
}
}
}
复制代码
LruResourceCache继承了LruCache,实现了资源被移除的监听和系统缓存当前程序的不同级别的内存处理。
Lru缓存的实现
一般而言Lru缓存实现有两种方式,
- 继承LinkedHashMap 重写removeEldestEntry方法,removeEldestEntry会在每次数据插入完成后进行调用,根据他的返回值决定是不是移除LinkedHashMap的链表表头。
- 引用LinkedHashMap 自己实现相应的移除操作。
这里Glide使用的是方式2,在每次put完成后会调用evict()
//将缓存的数据减小到指定大小以内。
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);
}
}
private void evict() {
trimToSize(maxSize);
}
复制代码
小结
Glide进行图片加载的时候会先从活动资源中进行获取,如果获取成功会对其引用计数器进行+1操作,以记录正在使用该资源的个数。如果从活动资源获取失败,会尝试从Lru缓存中获取,这个获取是从Lru缓存中移除,如果能够返回对应的资源,则将其加入活动资源缓存并对其引用计数器执行+1操作。否则,尝试从文件或者网络中获取。
为什么要有活动资源缓存?
活动资源缓存的存在以较小的代价减小Lru缓存的压力,提升Lru缓存的效率。
原因是活动资源缓存通过缓存的对象本身就是在内存中进行使用,缓存是只是建立一个弱引用关系。如果过没有活动资源缓存,每一次使用的资源都加入内存缓存,极有可能因为放入Lru缓存的数据过多,导致正在使用资源从Lru缓存中移除,等到下次来进行加载的时候因为没有对应的引用关系,找不到原来内存中正在使用的那个资源,从而需要再次从文件或者网络进行数据加载。这样同一份资源需要使用两处或者多处内存。大大的提高了内存消耗。总而言之,活动资源缓存以较小的代价提高了Lru缓存的使用效率,防止加载中的资源被lru回收。