前言

在前面一篇文章中,主要分析了Glide的工作流程,以加载网络图片为例分析了Glide是如何工作的。在熟悉了Glide的工作流程后,我们就可以及继续一些细节的分析。接下来,针对Glide的缓存策略进行分析。

我们知道,一个高效的图片框架是少不了缓存的,使用缓存可以减少资源的重复加载,提高资源的利用率。在Glide中,缓存分为两大类:内存缓存以及硬盘缓存。具体到缓存类型可以分为4种,一下是官网给出的缓存类型。

  • 活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
  • 内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
  • 资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?
  • 数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?

可以看到,内存缓存分为活动资源以及内存资源;硬盘缓存分为是否有处理过的资源以及原图资源。接下来主要从缓存的更新以及如何缓存两方面分析。

Glide的缓存

缓存键

缓存键是查找的缓存的一个键值,在Glide中需要根据资源的信息构造缓存键,然后查找缓存资源。在分析Glide的工作流程时,在类Engine中开始加载资源时,我们可以看到构造缓存键。

public <R> LoadStatus load(/**省略参数**/) {EngineKey key =keyFactory.buildKey(model,signature,width,height,transformations,resourceClass,transcodeClass,options);
}

在Engine的load()方法中可以看到在生成EngineKey的过程中用到了很多参数,比如model资源途径、signature签名、宽高等。这多种参数共同决定了缓存键的生成。

内存缓存

上面说到内存缓存分为活动资源和内存资源(这里先将另外一种资源称为内存资源)。其中活动资源是正在使用的图片也就是正在View中展示的,内存资源是存在内存中的,没有在使用。可以看到,Glide在内存缓存这里将资源又分为了两类。

我继续从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()方法。

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是先从活动资源取缓存,如果没有相应的活动资源就从内存资源中取缓存。接着往下看。

private EngineResource<?> loadFromActiveResources(Key key) {EngineResource<?> active = activeResources.get(key); //取出活动资源if (active != null) {active.acquire();}return active;}

在代码可以看到是从activeResources取出的活动资源。我们在这里跟一下这变量可以看到是在Engine的构造函数中进行的初始化。activeResources是ActiveResources类型的变量。我们直接在类ActiveResources看接下来的逻辑。

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();}}/*** 获取活动资源*/@Nullablesynchronized 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,资源以弱引用存储。

  • 查:获取资源的时候直接从Map中取出,然后从弱引用中拿到资源,如果资源为null就回收资源。
  • 增:增加资源的时候直接插入到Map容器中,如果存在旧资源就回收旧资源。
  • 删:删除资源的时候直接从Map容器中删除,然后回收资源。

以上就是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算法。

  • 查:获取资源很简单,直接从容器中获取。
  • 增:增加资源时,先判断容器是否已满,如果满了会调用onItemEvicted()方法回收资源,否则返回查到的资源。
  • 删:直接从容器中删除。

上面的过程涉及到了内存资源的三个管理过程:

  1. 获取资源时先从活动资源中获取。
  2. 活动资源没有就从内存资源中获取。
  3. 从内存资源中获取资源的同时将资源从活动资源中删除并加入到活动资源中。

这三步可以看到资源从内存资源到活动资源有个提升,但是内存缓存的资源时来自哪里还没有分析,我们接着往下将。内存缓存作为第一级缓存,它的来源肯定是从硬盘缓存或者网络这些途径。我们从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中有哪些硬盘管理策略。

  • DiskCacheStrategy.NONE: 表示不缓存任何内容。
  • DiskCacheStrategy.SOURCE: 表示只缓存原始图片。
  • DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)。
  • DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
  • DiskCacheStrategy.AUTOMATIC:智能的选择策略。

目前版本中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 AvoidInstantiatingObjectsInLoopshelper.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算法。

内存缓存分为活动资源和内存资源,活动资源的优先级高于内存资源。同时活动资源被回收时会将资源加入到内存资源中,在从内存资源中获取到资源时会将资源加入到活动资源。

硬盘缓存根据缓存策略来不同表现不同,在资源首次获取时会将资源加入到硬盘资源。

Glide学习(二)—缓存策略相关推荐

  1. 通过 Node.js 小示例学习浏览器缓存策略

    单纯讲一些理论性的东西可能会很难理解,本文结合一些 Node.js 小示例来学习浏览器缓存策略. 在后端为了加速服务的访问速度,通常可以使用 Memcached.Redis 做数据缓存,那么在浏览器端 ...

  2. Glide 4.9源码解析-缓存策略

    本文Glide源码基于4.9,版本下载地址如下:Glide 4.9 前言 在分析了Glide的图片加载流程后,更加发觉到Glide的强大,于是这篇文章将继续深入分析Glide的缓存策略.不过今天的文章 ...

  3. Glide核心设计二:缓存管理

    原文链接:Glide核心设计二:缓存管理 引言 Glide作为一个优秀的图片加载框架,缓存管理是必不可少的一部分,这篇文章主要通过各个角度.从整体设计到代码实现,深入的分析Glide的缓存管理模块,力 ...

  4. Android Glide缓存策略

    一.glide缓存策略 缓存在请求网络图片时能减少不必要的流量浪费.Glide 缓存分为内存缓存和硬盘缓存,这两个缓存模块的作用各不相同,内存缓存的主要作用是 防止应用重复的将图片数据读取到内存,而硬 ...

  5. glide默认的缓存图片路径地址_手写一个静态资源中间件,加深了解服务器对文件请求的缓存策略...

    上一篇文章<详解页面静态资源的缓存策略,搞懂强缓存和协商缓存再做性能优化>我们从理论上介绍了浏览器和服务器是如何对静态资源做缓存的,这篇文章我们把它做成一个node服务器的静态资源中间件. ...

  6. Glide 缓存策略 内存缓存和磁盘缓存

    感恩原创:http://www.cnblogs.com/baiqiantao/p/6808457.html Glide 缓存策略 内存缓存和磁盘缓存 官方文档:https://github.com/b ...

  7. 字节二面,让写一个LFU缓存策略算法,懵了

    LRU全称 "Least Recently Used",最近最少使用策略,判断最近被使用的时间,距离目前最远的数据优先被淘汰,作为一种根据访问时间来更改链表顺序从而实现缓存淘汰的算 ...

  8. android缓存策略跟cdn,缓存学习(五)CDN缓存(下)-CDN缓存策略、CDN缓存和浏览器缓存之间的关系、回源和回源比...

    CDN缓存策略 CDN(Content Delivery network,内容分发网络),通过GSLB技术使得用户能访问到最近物理机房的文件,以节省网络时间,也就是说一份文件可能会在全国乃至全球的多个 ...

  9. 【Android 内存优化】Bitmap 内存缓存 ( Bitmap 缓存策略 | LruCache 内存缓存 | LruCache 常用操作 | 工具类代码 )

    文章目录 一.Bitmap 内存缓存策略 二.LruCache 内存缓存 三.LruCache 常用操作 四.LruCache 工具类 五.源码及资源下载 官方参考 : Google 官方提供的 内存 ...

  10. 彻底弄懂 HTTP 缓存机制 —— 基于缓存策略三要素分解法

    导语 HTTP 缓存机制作为 Web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必须要掌握的知识,但最近我遇到了几个缓存头设置相关的题目,发现有好几道题答错了,有的甚至在知道了正确答案后 ...

最新文章

  1. 用计算机能改装成万用表吗,用旧手机电池修改万用表
  2. C#读取excel文件数据丢失问题
  3. Effect Java 学习笔记-对象的创建与销毁
  4. mysql导入数据表大小限制,解除phpMyAdmin导入大型MySQL数据库文件大小限制
  5. java清空redis缓存数据库_java相关:Spring Cache手动清理Redis缓存
  6. xshell连接服务器失败_xshell-ssh连接服务器被经常意外中断
  7. Python:Sklearn概述
  8. 分治 —— 01 分数规划
  9. UDF-java获取名字中的姓
  10. ios 自定义拍照页面_30分钟搞定iOS自定义相机
  11. 关于Backup Exec的Agent启动失败的解决办法
  12. win7计算机用户名在哪改,win7系统怎么更改用户账户名称|win7修改用户名的方法...
  13. ElementUI 面试题整理
  14. 微信群助手(自动整理拼车信息)
  15. 用MATLAB绘制两个圆相切,教您用几何绘图工具画三个两两相切的圆
  16. Android 系统原生TTS使用
  17. 电子签名就是数字签名吗?
  18. 清点代码库 (25 分)
  19. 什么是uuid以及uuid在java中的使用
  20. IDEA中 Maven Projects 窗口如何显示

热门文章

  1. 用BVP一比一还原自如客APP裸眼3D效果(Android原生)
  2. 域名实名认证多长时间_域名的实名认证需要多少时间
  3. HC、JD、OC是啥-求职、工作中可能会遇到的英文缩写
  4. 解决api打开显示“已取消到该网页的导航”问题或者api里面没有内容
  5. 利用SS7漏洞可追踪全球数十亿部手机 黑客千里之外窃听澳洲议员
  6. vba宏是什么,如何操作
  7. iPhone iPad分辨率
  8. ICE的Timer和TimerTask
  9. 获取一组坐标的中心点
  10. PostScript 与 Encapsulated PostScript