Glide源码学习笔记四:内存二级缓存
文章目录
前言
上一章Glide源码学习笔记三:into方法做了什么?阅读into(target)方法,构建了有无缩略图的几种情况的Request,来到Engine.load()方法。
Engine
先看看类注解是如何描述的:
/** Responsible for starting loads and managing active and cached resources. */
/** 负责开始下载以及管理内存和缓存资源 */
public class Engine
implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
......
......
}
Engine就是负责加载以及资源控制的核心类,在Glide初始化就已经设置完成。
接着看load方法,代码不长,参数有点多,给出了注释,方便大家理解:
public <R> LoadStatus load(
GlideContext glideContext,//整个Glide的上下文
Object model,//图片资源,可能是一个url字符串,也可能是一个resourceId等等
Key signature,//requestOptions的签名
int width,//图片宽
int height,//图片高
Class<?> resourceClass,//资源类型,RequestManager的asBitmap和asGif额外设置的选项
Class<R> transcodeClass,//解码类型,如bitmap,gif,drawable
Priority priority,//加载优先级
diskCacheStrategy,//缓存策略,缓存的数据有两种,未解码和已解码,对于这两种数据是否缓存的组合策略
Map<Class<?>, Transformation<?>> transformations,//图片剪裁的转换器,宽高,圆角等等
boolean isTransformationRequired,//是否转换
boolean isScaleOnlyOrNoTransform,//设置了fitCenter或CenterInside为true
Options options,//实现了Key接口,使用CachedHashCodeArrayMap保存若干个Option,而Option是保存缓存质量以及缓存格式的类
boolean isMemoryCacheable,//是否缓存,默认为true
boolean useUnlimitedSourceExecutorPool,//是否使用不受限制的缓存池
boolean useAnimationPool,//是否使用动画池,一般是加载gif图使用
boolean onlyRetrieveFromCache,//是否只在缓存中索引资源
ResourceCallback cb,//回调
Executor callbackExecutor//线程池) {
//参数过多,不用强行记忆,后续分析都会用到
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
//复用加载时,内存缓存的key
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
//通过上文的key在内存中检索资源
synchronized (this) {
//从内存获取资源
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
//未获取到,等待已经开始的加载或者开始一个新任务加载
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}
// Avoid calling back while holding the engine lock, doing so makes it easier for callers to
// deadlock.
//回调onResourceReady,就是在之前分析onSizeReady()未分析的方法
cb.onResourceReady(
memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return null;
}
上段代码中,loadFromMemory()
方法获取内存缓存,waitForExistingOrStartNewJob()
等待已经存在的资源加载完成或者开始新的加载任务,onResourceReady()
回调资源已经准备好。
这三个方法就是开始加载后资源获取的逻辑,逐个分析一下:
loadFromMemory()
@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
//如果跳过了内存缓存,直接返回null
if (!isMemoryCacheable) {
return null;
}
//从ActiveResources中检索资源,ActiveResources中保存的是正在使用,也就是被其他对象所持有的资源
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
//从LruResourceCache内存缓存中索引资源
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
从代码中可以很明显看出,Glide的内存缓存其实是有两级的,一个是正在使用的资源ActiveResources一个是经常说的使用Lru算法构建的缓存队列,ActiveResources优先级更高(特别强调,v4.4.0版本及之后版本是如此,之前的版本是Lru队列优先级更高)
。
接下来看看这两个缓存容器:
loadFromActiveResources()
在Enigine的构造方法中有这一行代码:
activeResources = new ActiveResources(isActiveResourceRetentionAllowed)
这里也有一个开关,是否开启正在使用的资源缓存。以下是ActiveResources关键方法,代码做了省略
final class ActiveResources {
......
......
//缓存容器,资源文件是弱引用包装的EngineResource,是一个弱引用,是ActiveResources 的内部类
@VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
//资源引用对列
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
//设置资源是活动的,也就是被其他对象引用了
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();
}
}
//通过key获取资源
@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;
}
......
......
}
有一些逻辑还不是很理解,比如如何保证资源的活动属性?最近最少算法如何实现?先不急,看看其他的逻辑。
loadFromCache()
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
//这个方法是EngineResource的,++了一个int变量
cached.acquire();
//ActiveResources代码中的activate,在这里调用了,赋予了资源的活动属性
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
//获取资源,是从成员变量的MemoryCache中索引找到
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 {
//缓存的不是EngineResource类型,就构造一个
result =
new EngineResource<>(
cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
提出问题
代码逻辑不多,但是有几个重点
逻辑需要理解:
1.EngineResource的acquire()方法有什么作用?
2.为什么要在获取到了资源的时候给调用ActiveResources的activate方法?
3.MemoryCache和ActiveResources有什么区别?
解决问题
进入EngineResource类中,代码部分省略:
class EngineResource<Z> implements Resource<Z> {
......
private int acquired;
......
//该方法在两层内存缓存获取资源的方法都有调用,同步方法,当资源被获取引用的时候,acquired+1
synchronized void acquire() {
//如果正在回收资源,就不能设置
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
//自增
++acquired;
}
//资源被回收
@Override
public synchronized 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;
if (isRecyclable) {
resource.recycle();
}
}
void release() {
boolean release = false;
synchronized (this) {
//acquired 小于等于0,说明没有被引用,也就无需释放资源
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (--acquired == 0) {
release = true;
}
}
if (release) {
//看下一个方法
listener.onResourceReleased(key, this);
}
}
}
/*这里还有一个方法,是Engine的回调*/
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
//从activeResources中移除
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
//加入MemoryCache中
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
}
}
回答问题
现在就可以回答问题了:
1.EngineResource的acquire()方法有什么作用?
与其说acquire()有什么作用,不如说EngineResource内部维护了一个int变量acquired,代表着引用次数。这也是ActiveResources是否缓存该资源的唯一标准:acquired(引用次数)大于0
。
2.为什么要在获取到了资源的时候给调用ActiveResources的activate方法?
在调用activate()之前,EngineResource调用了acquire()方法将引用次数+1,而activate只是为了更新缓存容器中存放的EngineResource的acquired值。
3.MemoryCache和ActiveResources有什么区别与联系?
容器不同:
MemoryCache真正的实现类为LruResourceCache,而LruResourceCache继承自LruCache,内部维护的是一个初始容量为100的LinkedHashMap。
ActiveResources内部是维护一个HashMap,将资源包装弱引用之后存储。
缓存策略不同:
MemoryCache插入的新数据是ActiveResources释放的那个EngineResource。而移除一个数据,是根据Lru算法的策略来的。有兴趣的同学可以自行查找资料。还有就是当获取成功一个资源成功,就添加到ActiveResources中,MemoryCache就没有必要保存它了。
ActiveResources插入数据的节点有两个,一个是根据Key读取ActiveResources没有找到却在MemoryCache中找到了,另外一个是Engine.onEngineJobComplete()回调,该方法还没有阅读过,在后续章节中解答。而删除数据,则是当EngineResource的acquired为0时触发,而该待删除的资源会触发插入MemoryCache中的逻辑,但是不保证成功。
总结
二级缓存的逻辑并不复杂,需要注意的是ActiveResources与MemoryCache的缓存策略。细节在回答问题中。
若大家有不同意见,欢迎讨论,一起学习一起进步!