Glide 4.12图片框架之多级缓存源码设计分析
Posted hymKing
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Glide 4.12图片框架之多级缓存源码设计分析相关的知识,希望对你有一定的参考价值。
一、Glide缓存初识
在上两篇文章中,我们从源码角度分析Glide框架加载图片的流程、以及Glide图片通过巧妙的空view的Fragment的设计实现的Glide的图片加载的三大生命周期函数onStart、onStop、onDestroy。Glide的框架的源码量确实比较大,今天我们再详细分析一下,Glide的框架的缓存模块的设计。
默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:
- 活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
- 内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
- 资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?
- 数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?
前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。
如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。
上述这段描述来源于官方文档对于缓存的介绍。官方文档的介绍,已经很清晰的说明了Glide框架在缓存实现的策略。缓存的类型在这里被分为了四类:活动资源、内存缓存、资源类型(磁盘缓存)、数据来源(文件缓存)。
其中活动资源、内存缓存 均属于内存缓存范畴、资源类型、数据来源 均属于磁盘缓存范畴。接下来,我们就从源码角度,来看一下这四级缓存的实现。
二、四级缓存实现的源码分析
结合之前的两篇Glide的分析的文章,我们确认缓存的分析的入口方法为Engine.load的方法,接下来我们就直接再次查看Engine类的相关源码实现:
2.1 活动资源的获取和添加
/** Engine类的功能:对启动的一个加载做响应和管理者活动资源和缓存资源 */
public class Engine imp...
public <R> LoadStatus load(...)
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
//key的创建,一份资源(比如图片)的唯一标识
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
synchronized (this)
//从内存中获取资源
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
//memoryResource=null,表示活动资源、内存缓存均未获取到
if (memoryResource == null)
//等待已经存在或者启动一个新的job
return waitForExistingOrStartNewJob(
....);
//从内存中获取资源
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime)
//是否启动内存缓存,构建glide的时候可以配置,无的的则直接返回空
if (!isMemoryCacheable)
return null;
//根据key从活动资源中获取
EngineResource<?> active = loadFromActiveResources(key);
//活动资源不为空
if (active != null)
//将活动资源返回
return active;
//活动资源为空,从cache中获取
EngineResource<?> cached = loadFromCache(key);
if (cached != null)
//cache资源不为空,将cache资源返回
return cached;
//活动资源和cache资源都为空,则最终返回null
return null;
从上述源码中,主要看loadFromMemory()方法的实现,流程上,先从活动内存获取缓存,如果获取到直接向上层返回,如果获取不到从cache缓存中获取,如果获取到将cache缓存返回,获取不到则返回空。接下来分别具体看活动缓存和cache缓存的究竟:
private final ActiveResources activeResources;
@Nullable
private EngineResource<?> loadFromActiveResources(Key key)
EngineResource<?> active = activeResources.get(key);
if (active != null)
//这个方法是在引擎的resource中定义的,做引用计数
active.acquire();
return active;
/**
* 资源包装器
*/
class EngineResource<Z> implements Resource<Z>
//获得引用数
synchronized void acquire()
if (isRecycled)
throw new IllegalStateException("Cannot acquire a recycled resource");
//获得活动资源引用数+1
++acquired;
//释放引用数
void release()
boolean release = false;
synchronized (this)
if (acquired <= 0)
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
if (--acquired == 0)
release = true;
if (release)
//从活动缓存中释放、迁入cache缓存中
listener.onResourceReleased(key, this);
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource)
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable())
//迁入cache缓存
cache.put(cacheKey, resource);
else
resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
活动资源是谁来维护的,我们可以看到activeResources.get(key),ActivityResources是活动资源的维护对象,ActivityResources是啥你,追踪一下源码:
final class ActiveResources
private final boolean isActiveResourceRetentionAllowed;
private final Executor monitorClearedResourcesExecutor;
//value 为弱引用对象Map集合
@VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
//引用队列
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
.....
//活动资源集合激活:本质就是向map集合中添加弱引用资源对象
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();
//get方法,从map中获取弱应用 活动资源对象
@Nullable
synchronized EngineResource<?> get(Key key)
//核心就这句,通过key从map集合中获取
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null)
return null;
EngineResource<?> active = activeRef.get();
if (active == null)
cleanupActiveReference(activeRef);
return active;
.....
这块代码逻辑比较清晰,活动资源从ActivityResource中维护的activeEngineResources(一个根据资源key,存储着弱引用资源的Map集合)来进行维护。get(key)是获取,同时我们也查看一下激活的逻辑,通过查看activate方法调用来自于engine类中:
@SuppressWarnings("unchecked")
@Override
public synchronized void onEngineJobComplete(
EngineJob<?> engineJob, Key key, EngineResource<?> resource)
if (resource != null && resource.isMemoryCacheable())
//激活活动缓存
activeResources.activate(key, resource);
jobs.removeIfCurrent(key, engineJob);
private EngineResource<?> loadFromCache(Key key)
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null)
cached.acquire();
//激活活动缓存
activeResources.activate(key, cached);
return cached;
小结:
活动资源通过引用计数法确定正在在被使用的资源。这样的资源被维护在内存中的一个Map集合中。活动资源的添加,很显然有两种手段,一种从远程获取到图片资源被使用后,会添加到活动资源,对应上述源码中的第一种情况,另一种是,从cache缓存中获得资源后,会添加活动资源,对应上述源码中的第二种情况。
而活动资源的移除,则是当当前资源不再有任何使用时,会从活动资源集合中移除,并添加到cache缓存中。
2.2 cache资源的获取和添加
2.1节介绍的活动缓存源码分析中,当活动缓存获取结果为空是,会继续调用loadFromCache()加载内存缓存。
public class Engine
implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener
//加载cache缓存
private EngineResource<?> loadFromCache(Key key)
//真实调用者
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null)
cached.acquire();
activeResources.activate(key, cached);
return cached;
//cache 是个momerycache
private final MemoryCache cache;
private EngineResource<?> getEngineResourceFromCache(Key key)
//从memeryCache中获取的
Resource<?> cached = cache.remove(key);
/下面的代码是包装成engineResource
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
result =
new EngineResource<>(
cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
return result;
这里我们重点关注memoryCache的实现LruResourceCache:
/** Lru 内存资源 */
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache
private ResourceRemovedListener listener;
public LruResourceCache(long size)
super(size);
public class LruCache<T, Y>
private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);
public synchronized Y remove(@NonNull T key)
Entry<Y> entry = cache.remove(key);
if (entry == null)
return null;
currentSize -= entry.size;
return entry.value;
Lru的特点,这里不做详细介绍了,可自行查资料,Lru主要说明的是集合的维护策略,英文:Least Recently Used,最近最少使用的资源会从集合中被淘汰。很多缓存实现都使用了Lru的算法。
关于Lrucache缓存的添加,2.1节中活动缓存的移除中,已经说明,当活动缓存不在使用,被释放时,会被添加的Lrucache中。
2.3对活动资源和LruCache缓存设计的思考和总结
近期研究的一些框架,无论客户端架构设计还是服务端架构设计,都会面临一个问题:性能。
而貌似解决性能问题的手段,又大有相似,无论是网络请求队列的设计、网络缓存的设计、还是图片加载队列的设计、图片缓存的设计,甚至于大型服务端架构、都不离开缓存设计,离不开优秀的缓存架构策略。而这些缓存的设计策略上,往往又都涉及多级缓存架构。
Glide框架中,活动资源是正在被使用的资源,目的是为了减少LruCache的缓存压力,也能避免同一份资源可能被加载两次的可能性。比如当前应用使用了大量的图片资源展示,如果Lrucache有可能会移除一份正在使用的图片资源,当这个资源需要将被被另一个视图所用之时,则可能需要重新从磁盘缓存中进行加载,这样的资源消耗是双份。
当然,上面的论断,是为了分析活动缓存设计的必要性思考出来的理由,也许是设计者的精益求精,大多数场景下,我觉得没有活动缓存的设计,并无影响。
Glide的设计上,目标对象先使用活动资源、资源后入lrucache ,另一种方案,资源直接入lrucache、驱动目标对象再使用,这是两种不同的设计、后者数据源统一,唯一的数据源驱动Ui视图更新。而入lrucache的动作和编解码无关,代码执行效率是极高的,所以,我认为即使采用后者的设计方案,也不会带来过大的性能问题。
(对这块设计的思考,有不同理解的朋友,欢迎在评论区讨论)
2.4磁盘缓存的读取和写入
在这篇文章 《最新源码Glide4.12框架之加载图片流程源码分析》 中我们分析了详细的图片加载流程,其中不涉及缓存模块的逻辑,再引用一下这篇文章末尾的流程图。
2.4.1磁盘缓存的读取
从前文的分析以及上述流程图中,我们知道真正执行加载任务的起点是EngineJob使用线程池执行DecodeJob。接下来,我们就从此处入手,继续分析磁盘缓存的相关内容。
class DecodeJob<R>
implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable
//在DecodeJob中最终调用的方法是此方法
private void runWrapped()
switch (runReason)
case INITIALIZE:
//初始状态
stage = getNextStage(Stage.INITIALIZE);
//磁盘缓存策略如果配置了,则返回的是Stage.RESOURCE_CACHE
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
private Stage getNextStage(Stage current)
switch (current)
case INITIALIZE:
//磁盘缓存策略如果配置了,则返回的是Stage.RESOURCE_CACHE
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE
: getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE
: getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
private DataFetcherGenerator getNextGenerator()
switch (stage)
//如果是磁盘缓存,此时构建的是DataFectherGenerator是ResourceCacheGenerator
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
通过上述源码的调用分析,如果磁盘缓存策略配置为可用,则最终是用的是ResourceCacheGenerator生成器。之后调用runGenerator():
private void runGenerators()
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled
&& currentGenerator != null/**此处调用当前generator的startNext方法*/
&& !(isStarted = currentGenerator.startNext()))
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE)
reschedule();
return;
if ((stage == Stage.FINISHED || isCancelled) && !isStarted)
notifyFailed();
DataFetcherGenerator的实现类,在glide框架的设计中有三个,分别为DataCacheGenerator、ResourceCacheGenerator、SourceGenerator。这里面是是ResourceGenerator。查看ResourceGenerator的startNext方法:
/**
* Generates @link com.bumptech.glide.load.data.DataFetcher DataFetchers from cache files
* containing downsampled/transformed resource data.
*/
class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object>
public boolean startNext()
//缓存key的集合列表
List<Key> sourceIds = helper.getCacheKeys();
以上是关于Glide 4.12图片框架之多级缓存源码设计分析的主要内容,如果未能解决你的问题,请参考以下文章