文章目录

  • 一、缓存简介
  • 二、缓存用法
    • 内存缓存方式
    • 磁盘缓存方式
  • 三、缓存KEY
  • 四、内存缓存
    • 内存缓存流程
  • 五、磁盘缓存
    • 磁盘缓存流程

Android Glide图片加载框架系列文章

Android Glide图片加载框架(一)基本用法

Android Glide图片加载框架(二)源码解析之with()

Android Glide图片加载框架(二)源码解析之load()

Android Glide图片加载框架(二)源码解析之into()

Android Glide图片加载框架(三)缓存机制

一、缓存简介

Glide的缓存设计可以说是非常先进的,考虑的场景也很周全。在缓存这一功能上,Glide又将它分成了两个模块,一个是 内存缓存 ,一个是 磁盘缓存

这两个缓存模块的作用各不相同,

  • 内存缓存 的主要作用是防止应用重复将图片数据读取到内存当中;

  • 磁盘缓存 的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。

内存缓存硬盘缓存 的相互结合才构成了Glide极佳的图片缓存效果,那么接下来我们就分别来分析一下这两种缓存的使用方法以及它们的实现原理。

二、缓存用法

内存缓存方式

RequestOptions options = new RequestOptions();
// 禁止内存缓存
options.skipMemoryCache(true);Glide.with(this).load(url).apply(options).into(imageView);

磁盘缓存方式

RequestOptions options = new RequestOptions();
// 磁盘不缓存
options.diskCacheStrategy(DiskCacheStrategy.NONE);Glide.with(this).load(url).apply(options).into(imageView);

可以设置5种模式:

  • DiskCacheStrategy.NONE: 表示不缓存任何内容

  • DiskCacheStrategy.DATA: 表示只缓存原始图片

  • DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片

  • DiskCacheStrategy.ALL: 表示既缓存原始图片,也缓存转换过后的图片

  • DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)

三、缓存KEY

既然是缓存功能,就必然会有用于进行缓存的Key。那么Glide的缓存Key是怎么生成的呢?我不得不说,Glide的缓存Key生成规则非常繁琐,决定缓存Key的参数竟然有8个之多。不过繁琐归繁琐,至少逻辑还是比较简单的,我们先来看一下Glide缓存Key的生成逻辑。

生成缓存Key的代码在 Engine 类的 load() 方法当中,这部分代码我们在上一篇文章当中已经分析过了,只不过当时忽略了缓存相关的内容,那么我们现在重新来看一下:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...public <R> LoadStatus load(GlideContext glideContext,Object model,Key signature,int width,int height,Class<?> resourceClass,Class<R> transcodeClass,Priority priority,DiskCacheStrategy diskCacheStrategy,Map<Class<?>, Transformation<?>> transformations,boolean isTransformationRequired,boolean isScaleOnlyOrNoTransform,Options options,boolean isMemoryCacheable,boolean useUnlimitedSourceExecutorPool,boolean useAnimationPool,boolean onlyRetrieveFromCache,ResourceCallback cb) {Util.assertMainThread();long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,resourceClass, transcodeClass, options);...}...
}

第27行可见,决定缓存Key的条件非常多,即使你用override()方法改变了一下图片的width或者height,也会生成一个完全不同的缓存Key。

缓存key是一个 EngineKey 对象,该类重写了 equals()hashCode() 方法,保证只有传入EngineKey的所有参数都相同的情况下才认为是同一个 EngineKey 对象,代码如下:

class EngineKey implements Key {...public boolean equals(Object o) {if (o instanceof EngineKey) {EngineKey other = (EngineKey) o;return model.equals(other.model)&& signature.equals(other.signature)&& height == other.height&& width == other.width&& transformations.equals(other.transformations)&& resourceClass.equals(other.resourceClass)&& transcodeClass.equals(other.transcodeClass)&& options.equals(other.options);}return false;}@Overridepublic int hashCode() {if (hashCode == 0) {hashCode = model.hashCode();hashCode = 31 * hashCode + signature.hashCode();hashCode = 31 * hashCode + width;hashCode = 31 * hashCode + height;hashCode = 31 * hashCode + transformations.hashCode();hashCode = 31 * hashCode + resourceClass.hashCode();hashCode = 31 * hashCode + transcodeClass.hashCode();hashCode = 31 * hashCode + options.hashCode();}return hashCode;}...
}

四、内存缓存

默认情况下,Glide自动就是开启内存缓存的 。也就是说,当我们使用Glide加载了一张图片之后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除之前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。比方说你在一个RecyclerView当中反复上下滑动,RecyclerView中只要是Glide加载过的图片都可以直接从内存当中迅速读取并展示出来,从而大大提升了用户体验。

而Glide最为人性化的是,你甚至不需要编写任何额外的代码就能自动享受到这个极为便利的内存缓存功能,因为Glide默认就已经将它开启了。

那么既然已经默认开启了这个功能,还有什么可讲的用法呢?只有一点,如果你有什么特殊的原因需要禁用内存缓存功能,Glide对此提供了接口:

RequestOptions options = new RequestOptions();
options.skipMemoryCache(true);Glide.with(this).load(url).apply(options).into(img);

可以看到,只需要调用skipMemoryCache()方法并传入true,就表示禁用掉Glide的内存缓存功能

接下来就让我们就通过阅读源码来分析一下Glide的内存缓存功能是如何实现的。

内存缓存使用弱引用和LruCache算法结合完成的,弱引用来缓存的是正在使用中的图片。图片封装类Resources内部有个计数器判断是该图片否正在使用。

内存缓存流程

  • 读: 是先从弱引用中取,取不到再从lruCache取;

  • 存: 内存缓存取不到,从网络拉取回来先放在弱引用里,渲染图片,图片对象Resources使用计数加一;

  • 渲染完图片: 图片对象Resources使用计数减一,如果计数为0,图片缓存从弱引用中删除,放入lruCache缓存。

上篇提到,Engine 在加载流程的中的入口方法是 load 方法:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...public <R> LoadStatus load(GlideContext glideContext,Object model,Key signature,int width,int height,Class<?> resourceClass,Class<R> transcodeClass,Priority priority,DiskCacheStrategy diskCacheStrategy,Map<Class<?>, Transformation<?>> transformations,boolean isTransformationRequired,boolean isScaleOnlyOrNoTransform,Options options,boolean isMemoryCacheable,boolean useUnlimitedSourceExecutorPool,boolean useAnimationPool,boolean onlyRetrieveFromCache,ResourceCallback cb) {Util.assertMainThread();long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;// 生成缓存keyEngineKey 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;}// 从LruCache获取缓存图片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;}EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);if (current != null) {current.addCallback(cb);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Added to existing load", startTime, key);}return new LoadStatus(cb, current);}EngineJob<R> engineJob =engineJobFactory.build(key,isMemoryCacheable,useUnlimitedSourceExecutorPool,useAnimationPool,onlyRetrieveFromCache);DecodeJob<R> decodeJob =decodeJobFactory.build(glideContext,model,key,signature,width,height,resourceClass,transcodeClass,priority,diskCacheStrategy,transformations,isTransformationRequired,isScaleOnlyOrNoTransform,onlyRetrieveFromCache,options,engineJob);jobs.put(key, engineJob);engineJob.addCallback(cb);engineJob.start(decodeJob);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);}...
}

上面是从内存缓存中读取图片的主流程:

  • 生成缓存的key。

  • 从弱引用获取图片。

  • 弱引用没取到,在从LruCache获取缓存图片。

  • 内存缓存取不到,进入异步处理。

我们具体看取图片的两个方法 loadFromActiveResources()loadFromCache()

  • loadFromActiveResources 使用的就是弱引用。

  • loadFromCache 使用的就是LruCache算法。

我们来看一下它们的源码:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {if (!isMemoryCacheable) {return null;}EngineResource<?> active = activeResources.get(key);if (active != null) {active.acquire();}return active;}private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {if (!isMemoryCacheable) {return null;}EngineResource<?> cached = getEngineResourceFromCache(key);if (cached != null) {cached.acquire();activeResources.activate(key, cached);}return cached;}private EngineResource<?> getEngineResourceFromCache(Key key) {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, true /*isMemoryCacheable*/, true /*isRecyclable*/);}return result;}...
}

loadFromActiveResources() 方法:

  • 首先就判断 isMemoryCacheable 是不是 false ,如果是false的话就直接返回null。这就是 skipMemoryCache() 方法设置的是否内存缓存已被禁用。

  • 然后从 activeResources 当中取值,使用activeResources来缓存正在使用中的图片,用来保护正在使用中的图片不会被LruCache算法回收掉。

loadFromCache() 方法:

  • 首先就判断 isMemoryCacheable 是不是 false ,如果是false的话就直接返回null。这就是 skipMemoryCache() 方法设置的是否内存缓存已被禁用。

  • 然后调用 getEngineResourceFromCache() 方法来获取缓存。在这个方法中,会从中获取图片缓存 LruResourceCache ,LruResourceCache其实使用的就是LruCache算法实现的缓存。

  • 当我们从 LruResourceCache 中获取到缓存图片之后会将它从缓存中移除,将缓存图片存储到 activeResources 当中。activeResources就是弱引用的HashMap,用来缓存正在使用中的图片。

这样我们把从内存读取图片缓存的流程搞清了,那是什么时候存储的呢。想想什么时候合适?是不是应该在异步处理获取到图片后,再缓存到内存?

EngineJob 获取到图片后 会回调Engine的 onEngineJobComplete() 。我们来看下做了什么:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {Util.assertMainThread();// A null resource indicates that the load failed, usually due to an exception.if (resource != null) {resource.setResourceListener(key, this);if (resource.isCacheable()) {activeResources.activate(key, resource);}}jobs.removeIfCurrent(key, engineJob);}...
}

onEngineJobComplete() 方法里将正在加载的图片放到弱引用缓存。那什么时候放在LruCache里呢?当然是在使用完,那什么时候使用完呢?

那我们来看 EngineResource 这个类是怎么标记自己是否在被使用的。EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,代码如下所示:

class EngineResource<Z> implements Resource<Z> {...private int acquired;void acquire() {if (isRecycled) {throw new IllegalStateException("Cannot acquire a recycled resource");}if (!Looper.getMainLooper().equals(Looper.myLooper())) {throw new IllegalThreadStateException("Must call acquire on the main thread");}++acquired;}void release() {if (acquired <= 0) {throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");}if (!Looper.getMainLooper().equals(Looper.myLooper())) {throw new IllegalThreadStateException("Must call release on the main thread");}if (--acquired == 0) {listener.onResourceReleased(key, this);}}...
}

可以看出当引用计数acquired变量为0,就是没有在使用了,然后调用了 listener.onResourceReleased(key, this);

这个 listener 就是 Engine 对象,我们来看下它的 onResourceReleased() 方法:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {Util.assertMainThread();activeResources.deactivate(cacheKey);if (resource.isCacheable()) {cache.put(cacheKey, resource);} else {resourceRecycler.recycle(resource);}}...
}

做了三件事:

  • 从弱引用删除图片缓存

  • 是否支持缓存,缓存到LruCache缓存

  • 不支持缓存直接调用垃圾回收,回收图片

到这里内存缓存的读和存的流程就介绍完了,根据源码回头看看我们之前列的Glide内存缓存流程,就清晰很多了。

五、磁盘缓存

磁盘缓存流程

  • 读: 先找处理后(result)的图片,没有的话再找原图。

  • 存: 先存原图,再存处理后的图。

注: diskCacheStrategy设置的的缓存模式即影响读取,也影响存储。

在判断了两级内存缓存之后,如果拿不到缓存,就会接着创建 EngineJobDecodeJob ,然后接着就会调用进 DecodeJob 线程的 run() 方法:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,Runnable,Comparable<DecodeJob<?>>,Poolable {...@Overridepublic void run() {// This should be much more fine grained, but since Java's thread pool implementation silently// swallows all otherwise fatal exceptions, this will at least make it obvious to developers// that something is failing.GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);// Methods in the try statement can invalidate currentFetcher, so set a local variable here to// ensure that the fetcher is cleaned up either way.DataFetcher<?> localFetcher = currentFetcher;try {if (isCancelled) {notifyFailed();return;}runWrapped();} catch (Throwable t) {// Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our// usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We// are however ensuring that our callbacks are always notified when a load fails. Without this// notification, uncaught throwables never notify the corresponding callbacks, which can cause// loads to silently hang forever, a case that's especially bad for users using Futures on// background threads.if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "DecodeJob threw unexpectedly"+ ", isCancelled: " + isCancelled+ ", stage: " + stage, t);}// When we're encoding we've already notified our callback and it isn't safe to do so again.if (stage != Stage.ENCODE) {throwables.add(t);notifyFailed();}if (!isCancelled) {throw t;}} finally {// Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call// close in all cases anyway.if (localFetcher != null) {localFetcher.cleanup();}GlideTrace.endSection();}}private void runWrapped() {switch (runReason) {case INITIALIZE:stage = getNextStage(Stage.INITIALIZE);currentGenerator = getNextGenerator();runGenerators();break;case SWITCH_TO_SOURCE_SERVICE:runGenerators();break;case DECODE_DATA:decodeFromRetrievedData();break;default:throw new IllegalStateException("Unrecognized run reason: " + runReason);}}...
}

run() 中主要还是调用的 runWrapper() 方法,继而调用 runGenerator()

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,Runnable,Comparable<DecodeJob<?>>,Poolable {...private void runGenerators() {currentThread = Thread.currentThread();startFetchTime = LogTime.getLogTime();boolean isStarted = false;while (!isCancelled && currentGenerator != null&& !(isStarted = currentGenerator.startNext())) {stage = getNextStage(stage);currentGenerator = getNextGenerator();if (stage == Stage.SOURCE) {reschedule();return;}}// We've run out of stages and generators, give up.if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {notifyFailed();}// Otherwise a generator started a new load and we expect to be called back in// onDataFetcherReady.}...
}

这里调用了一个循环获取解析生成器 Generator 的方法,而解析生成器有多个实现类:ResourcesCacheGeneratorSourceGeneratorDataCacheGenerator,它们负责各种硬盘缓存策略下的缓存管理,所以这里关键的条件在于 currentGenerator.startNext() 循环获取每个Generator能否获取到缓存,获取不到就通过 getNextGenerator() 进行下一种:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,Runnable,Comparable<DecodeJob<?>>,Poolable {...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);}}...
}

所以我们看看 ResourceCacheGenerator.startNext() ,看下它是用什么来缓存的,其中部分代码如下:

class ResourceCacheGenerator implements DataFetcherGenerator,DataFetcher.DataCallback<Object> {...public boolean startNext() {...while (modelLoaders == null || !hasNextModelLoader()) {...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;}...
}

这里通过一个资源的关键信息生成key,然后调用 helper.getDiskCache().get() ,我们跟进去 DiskCache 看看:

final class DecodeHelper<Transcode> {...DiskCache getDiskCache() {return diskCacheProvider.getDiskCache();}...
}
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,Runnable,Comparable<DecodeJob<?>>,Poolable {...interface DiskCacheProvider {DiskCache getDiskCache();}...
}

可以看到最终是调用了 DiskCacheProvider 接口的 getDiskCache() 方法获取一个 DiskCache 对象,那么这个D对象又是什么来头呢?

public interface DiskCache {...
}

可以看到这是一个用来缓存硬盘数据的接口,那么它的实现就是我们要找的最终目标:

public class DiskLruCacheWrapper implements DiskCache {...private DiskLruCache diskLruCache;...
}

里面的就不详细分析下去了,这里主要维护了一个 DiskLruCache ,Glide就是通过这个来实现硬盘缓存的。

可以看到Glide的硬盘缓存是依靠DiskLruCache来进行缓存的,同样也是Lru算法。

Android Glide图片加载框架(三)缓存机制相关推荐

  1. Android Glide图片加载框架(四)回调与监听

    文章目录 Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图片加载框架(二)源码解析之with() Android Gl ...

  2. Android Glide图片加载框架(二)源码解析之into()

    文章目录 一.前言 二.源码解析 1.into(ImageView) 2.GlideContext.buildImageViewTarget() 3.RequestBuilder.into(Targe ...

  3. Android Glide图片加载框架(二)源码解析之load()

    文章目录 一.前言 二.源码分析 1.load() Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图片加载框架(二)源 ...

  4. Android Glide图片加载框架(二)源码解析之with()

    文章目录 一.前言 二.如何阅读源码 三.源码解析 1.with() Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图 ...

  5. Android Glide图片加载框架(一)基本用法

    文章目录 一.前言 二.简介 三.基本用法 第一步:调用 Glide.with() 方法创建加载图片的实例 第二步:调用 load() 方法指定待加载的图片资源 第三步:调用 into() 方法绑定显 ...

  6. Android Glide 图片加载框架解析

    在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide 的图片加载框架,作者是  bumptech,这个库被广泛的应用在 Google 开源项目中,包括 2014 年 Google I/O ...

  7. Glide图片加载框架的使用

    1. 介绍 Glide是一个快速高效的Android图片加载库,注重于平滑的滚动.Glide提供了易用的API,高性能.可扩展的图片解码管道(decode pipeline),以及自动的资源池技术.G ...

  8. 优雅地实现Android主流图片加载框架封装,可无侵入切换框架

    项目开发中,往往会随着需求的改变而切换到其它图片加载框架上去.如果最初代码设计的耦合度太高,那么恭喜你,成功入坑了.至今无法忘却整个项目一行行去复制粘贴被支配的恐惧.:) 那么是否存在一种方式 能够一 ...

  9. android Glide图片加载库使用

    Glide是一款由Bump Technologies开发的图片加载框架,使得我们可以在Android平台上以极度简单的方式加载和展示图片. 要想使用Glide,首先需要将这个库引入到我们的项目当中.新 ...

最新文章

  1. Redis 入门安装(Linux)
  2. 新手科普 | 探索机器学习模型,保障账户安全
  3. 知乎高赞:我的编程能力从什么时候开始突飞猛进的?
  4. 循环神经网络-Dropout
  5. 第六天2017/04/11(1:结构体链表基础和相关经典操作)
  6. java 对象创建过程_5种创建Java对象的方式
  7. LibreOj 6279数列分块入门 3 练习了一下set
  8. BZOJ——2697: 特技飞行
  9. LeetCode--265. 粉刷房子Ⅱ(动态规划)
  10. JAVA GC(Garbage Collection)及OOM那些事
  11. C语言家谱管理程序,c语言的家谱——interesting~
  12. [导入]MsAjax Lib- Boolean 类型扩展
  13. 机器学习初级入门(一)感知机
  14. python 递归函数 内存底层_Python基础篇【第八篇】:剖析递归函数
  15. pdf阅读器怎么样去拆分文档啊
  16. win10系统自动打开代理服务器的解决方法
  17. 微擎支付返回商户单号_ThinkPHP6对接实现微信H5支付
  18. 主板4线风扇原理分析
  19. python数字金额转换为中文大写金额
  20. Python(求第五个人岁数)

热门文章

  1. 接口和抽象类是否继承了Object
  2. 一步步编写操作系统 19 改进MBR,直接操作显卡
  3. android 设置view最大高度,android-在RecyclerView上设置最大高度
  4. wince投屏苹果手机_怎么把手机上的导航映射到中控屏
  5. sleep期间读取所有_ceph部分数据所有副本先后故障的抢救
  6. 数据结构- 栈(实现综合计算器)(一位数计算 扩展到 多位数计算)
  7. Spark一些组件的定义
  8. 【POJ - 1050】To the Max (dp)
  9. 【HRBUST - 1613】迷宫问题 (bfs)
  10. 【51Nod - 1416】两点 (dfs 或 并查集+dfs)