在前面一篇文章中,主要分析了Glide的工做流程,以加载网络图片为例分析了Glide是如何工做的。在熟悉了Glide的工做流程后,咱们就能够及继续一些细节的分析。接下来,针对Glide的缓存策略进行分析。算法
咱们知道,一个高效的图片框架是少不了缓存的,使用缓存能够减小资源的重复加载,提升资源的利用率。在Glide中,缓存分为两大类:内存缓存以及硬盘缓存。具体到缓存类型能够分为4种,一下是官网给出的缓存类型。缓存
能够看到,内存缓存分为活动资源以及内存资源;硬盘缓存分为是否有处理过的资源以及原图资源。接下来主要从缓存的更新以及如何缓存两方面分析。网络
缓存键是查找的缓存的一个键值,在Glide中须要根据资源的信息构造缓存键,而后查找缓存资源。在分析Glide的工做流程时,在类Engine中开始加载资源时,咱们能够看到构造缓存键。app
public <R> LoadStatus load(/**省略参数**/) {
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
}
复制代码
在Engine的load()方法中能够看到在生成EngineKey的过程当中用到了不少参数,好比model资源途径、signature签名、宽高等。这多种参数共同决定了缓存键的生成。框架
上面说到内存缓存分为活动资源和内存资源(这里先将另一种资源称为内存资源)。其中活动资源是正在使用的图片也就是正在View中展现的,内存资源是存在内存中的,没有在使用。能够看到,Glide在内存缓存这里将资源又分为了两类。ide
我继续从Engine的load()方法分析。这里是真正开始加载资源的入口。函数
public <R> LoadStatus load(/**省略参数**/) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
synchronized (this) {
//从内存中加载资源
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
return waitForExistingOrStartNewJob(/**省略参数**/);
}
}
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}
复制代码
从代码里能够看到Glide先从内存中加载资源,这里调用了loadFromMemory()方法。oop
private EngineResource<?> loadFromMemory(EngineKey key, boolean isMemoryCacheable, long startTime) {
//这里是一个配置,使用skipMemoryCache(boolean skip)能够选择是否跳过从缓存中获取资源
if (!isMemoryCacheable) {
return null;
}
//从活动资源中取缓存
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
return active;
}
//从内存资源中取缓存
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
return cached;
}
return null;
}
复制代码
能够看到,在上面的过程当中,Glide是先从活动资源取缓存,若是没有相应的活动资源就从内存资源中取缓存。接着往下看。ui
private EngineResource<?> loadFromActiveResources(Key key) {
EngineResource<?> active = activeResources.get(key); //取出活动资源
if (active != null) {
active.acquire();
}
return active;
}
复制代码
在代码能够看到是从activeResources取出的活动资源。咱们在这里跟一下这变量能够看到是在Engine的构造函数中进行的初始化。activeResources是ActiveResources类型的变量。咱们直接在类ActiveResources看接下来的逻辑。this
final class ActiveResources {
@VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>(); //用来存储活动资源
/** * 将资源加入到活动资源中 */
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;
}
}
复制代码
这里咱们只分析活动资源的增、删、查三种操做,其余一些细节能够自行查看,好比ActiveResources在初始化的时候会清空队列。
从代码中能够看到ActiveResources是经过一个Map容器存储活动资源的,键就是EngineKey,资源以弱引用存储。
以上就是Glide管理活动资源的一部分操做,在加载资源时,先从活动资源中取出。我接着往下看,内存资源是如何操做的。
private EngineResource<?> loadFromCache(Key key) {
//获取内存资源
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
//将内存资源加入到活动资源中
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
复制代码
上面的代码作了两个工做,第一步先从内存资源中取,而后再将内存资源加入到活动资源中。咱们接下来查看内存资源如何取出。
private EngineResource<?> getEngineResourceFromCache(Key key) {
//从LruResourceCache中取出资源的同时将资源删除
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 {
result =
new EngineResource<>(
cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
复制代码
能够看到内存资源是从一个cache的变量中取出的。我跟代码能够看到cache是一个MemoryCache类型的变量,继续下去,能够看到在Glide初始化的时候会初始化MemoryCache。
/**GlideBuilder**/
Glide build(@NonNull Context context) {
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
}
复制代码
在上面的代码中,cache被初始化为LruResourceCache类型。除此以外,还有其余的一些资源被初始化,列出来的是硬盘缓存相关的,这里先不说,接下来再讲。
咱们继续查看LruResourceCache,从类名上能够看出内存资源是同LRU算法管理的。分析代码能够看到,LruResourceCache继承自LruCache,具体逻辑也是在LruCache中实现,因此我分析LruCache的实现。
public class LruCache<T, Y> {
private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
/** * 获取资源 */
public synchronized Y get(@NonNull T key) {
return cache.get(key);
}
/** * 增长资源 */
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;
}
/** * 删除资源 */
public synchronized Y remove(@NonNull T key) {
final Y value = cache.remove(key);
if (value != null) {
currentSize -= getSize(value);
}
return value;
}
}
复制代码
这里也只分析内存资源的增、删、查三种操做,其余细节能够自行查看。能够看到LruCache直接使用了LinkedHashMap做为容器存储资源,也就意味着LruCache直接使用了LinkedHashMap的LRU算法。
上面的过程涉及到了内存资源的三个管理过程:
这三步能够看到资源从内存资源到活动资源有个提高,可是内存缓存的资源时来自哪里尚未分析,咱们接着往下将。内存缓存做为第一级缓存,它的来源确定是从硬盘缓存或者网络这些途径。咱们从Glide工做流程中获取完资源后开始资源回调的流程开始分析。
void notifyCallbacksOfResult() {
// ......
incrementPendingCallbacks(copy.size() + 1);
//回调EngineJob完成
engineJobListener.onEngineJobComplete(this, localKey, localResource);
//......
decrementPendingCallbacks();
}
复制代码
在上面的代码中,资源完成后会回调onEngineJobComplete()方法,这个方式是Engine中的方法,
public synchronized void onEngineJobComplete( EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
if (resource != null && resource.isMemoryCacheable()) {
//加入到活动资源
activeResources.activate(key, resource);
}
jobs.removeIfCurrent(key, engineJob);
}
复制代码
在这个方法中就将获取到的资源(可能来自于硬盘缓存或者网络)加入到了活动资源中。除此以外还注意到其余两个函数incrementPendingCallbacks()以及decrementPendingCallbacks()。咱们看下他们作了什么事情。
synchronized void incrementPendingCallbacks(int count) {
Preconditions.checkArgument(isDone(), "Not yet complete!");
if (pendingCallbacks.getAndAdd(count) == 0 && engineResource != null) {
engineResource.acquire();
}
}
复制代码
synchronized void decrementPendingCallbacks() {
stateVerifier.throwIfRecycled();
Preconditions.checkArgument(isDone(), "Not yet complete!");
int decremented = pendingCallbacks.decrementAndGet();
Preconditions.checkArgument(decremented >= 0, "Can't decrement below 0");
if (decremented == 0) {
if (engineResource != null) {
engineResource.release();
}
release();
}
}
复制代码
从代码中能够看到,在incrementPendingCallbacks()方法中对资源执行了engineResource.acquire()操做,这个操做的做用就至关于记录资源的引用次数,同时记录有多少个回调在使用资源。而在decrementPendingCallbacks()方法中能够看到对回调数量执行了减操做,若是数量等于0,就释放资源,咱们看下资源是如何释放的。engineResource.release()执行了资源释放的操做,咱们跟着代码看下去,能够发现最终调用了Engine的onResourceReleased()方法。
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
复制代码
在这个方法中能够看到,资源从活动资源中删除,而后加入到了内存资源中。
到这里Glide对内存缓存的管理就大体这些内容,包括:从内存缓存中去资源的过程以及内存缓存如何更新。
在分析Glide硬盘缓存的管理机制前,咱们先了解一下Glide中有哪些硬盘管理策略。
目前版本中Glide一共有这5中缓存策略。每种策略所对应的行为都不相同,咱们下面会讲到。
接下来,仍是从Glide的工做流程中开始讲银盘缓存策略。
上面说到,在加载资源时会先从内存缓存中取,若是内存缓存中不存在对应的资源,那么加载流程会继续进行下去,咱们直接分析。在Glide的工做流程中咱们知道经过DecodeJob中获取各类类型的Generator加载资源。
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
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:
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
复制代码
能够看到,在getNextStage()方法中首先会根据硬盘缓存策略获取对应的Stage。首先根据decodeCachedResource()方法的结果选择是否使用硬盘缓存。在上面已经介绍过了Glide不一样的缓存策略,根据策略的不一样decodeCachedResource()的返回结果不一样。上面的5种策略中,NONE和DATA的返回值时false,NONE策略很好理解,由于它禁用了硬盘缓存。DATA策略的意思是直接获取以前缓存的数据,而其余的缓存策略会获取数据对应的资源。只其中的差别接下来会说到。
如今咱们说下,在使用硬盘缓存状况下的流程。那个getNextStage()方法会返回Stage.RESOURCE_CACHE。
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
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);
}
}
复制代码
在getNextGenerator()能够看到,Stage.RESOURCE_CACHE对应了ResourceCacheGenerator。咱们知道资源加载是在Generator的startNext()方法中执行的,咱们直接分析这个方法。
/**ResourceCacheGenerator**/
public boolean startNext() {
//......
Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = helper.getTransformation(resourceClass);
//构造缓存键
currentKey =
new ResourceCacheKey( // NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
//获取缓存资源
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
//......
return started;
}
复制代码
能够看到,首先构造了缓存键,而后再经过helper.getDiskCache().get(currentKey)获取资源。我跟下这里的代码,能够知道缓存是来自于DiskLruCacheWrapper。根据这个类名能够知道硬盘缓存也是LRU算法管理的。
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
File result = null;
try {
//经过DiskLruCache获取资源
final DiskLruCache.Value value = getDiskCache().get(safeKey);
if (value != null) {
result = value.getFile(0);
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to get from disk cache", e);
}
}
return result;
}
复制代码
完后上面的过程后就取得了硬盘资源。
除此以外,咱们刚才还说到了DATA类型的缓存策略,咱们看下这种策略的运行流程。上面说到DATA策略是直接获取原有的数据,根据DATA策略的返回结果,getNextStage()方法返回的是Stage.DATA_CACHE,对应到Generator就是DataCacheGenerator。
/**DataCacheGenerator**/
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
//......
return started;
}
复制代码
经过DataCacheGenerator的startNext()方法能够看到,DATA策略也是先构造缓存键而后经过DiskLruCache获取缓存的数据。
以上的就是硬盘缓存在不一样的策略下的获取过程,接下来咱们分析,何时将资源加入到硬盘缓存中。这个其实很容易想到在首次获取资源时会将资源加入到硬盘缓存中。经过DiskLruCacheWrapper中put()方法的调用,咱们能够看到在DecodeJob中会将资源加入到硬盘缓存中。
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
//......
//这个方法的回调会将资源加入到活动资源中
notifyComplete(result, dataSource);
stage = Stage.ENCODE;
try {
if (deferredEncodeManager.hasResourceToEncode()) {
//这里将资源加入到硬盘缓存中
deferredEncodeManager.encode(diskCacheProvider, options);
}
}
//......
}
复制代码
void encode(DiskCacheProvider diskCacheProvider, Options options) {
try {
diskCacheProvider
.getDiskCache()
.put(key, new DataCacheWriter<>(encoder, toEncode, options)); //将资源加入到硬盘缓存
}
//......
}
复制代码
能够看到在以上资源回调的过程当中,Glide完成了内存缓存以及硬盘缓存的管理。
到这里Glide的缓存策略就大体讲完了。上面的文章从缓存的获取以及缓存的管理量方面分析了Glide的缓存原理。
从缓存获取的角度来将,Glide在加载资源时:
从缓存管理来将:Glide管理的缓存分为内存缓存和硬盘缓存。缓存算法都是使用的LRU算法。
内存缓存分为活动资源和内存资源,活动资源的优先级高于内存资源。同时活动资源被回收时会将资源加入到内存资源中,在从内存资源中获取到资源时会将资源加入到活动资源。
硬盘缓存根据缓存策略来不一样表现不一样,在资源首次获取时会将资源加入到硬盘资源。