Glide 原理 4 - 缓存机制

围巾🧣 2024年04月10日 668次浏览
文章结构

文章结构

1. Glide缓存机制简介

1.1 缓存的图片资源

Glide 需要缓存的 图片资源 分为两类:

  • 原始图片(Source) :即图片源的图片初始大小 & 分辨率
  • 转换后的图片(Result) :经过 尺寸缩放 和 大小压缩等处理后的图片

当使用 Glide加载图片时,Glide默认 根据 View视图对图片进行压缩 & 转换,而不显示原始图(这也是Glide加载速度高于Picasso的原因)

1.2 缓存机制设计

  • Glide的缓存功能设计成 二级缓存:内存缓存 & 硬盘缓存

并不是三级缓存,因为 从网络加载 不属于缓存

  • 缓存读取顺序:内存缓存 --> 磁盘缓存 --> 网络
  1. 内存缓存 默认开启
  2. Glide中,内存缓存 & 磁盘缓存相互不影响,独立配置
  • 二级缓存的作用不同:

    1. 内存缓存:防止应用 重复将图片数据 读取到内存当中

    只 缓存转换过后的图片

    1. 硬盘缓存:防止应用 重复从网络或其他地方重复下载和读取数据

    可缓存原始图片 & 缓存转换过后的图片,用户自行设置

Glide的缓存机制使得 Glide具备非常好的图片缓存效果,从而使得具备较高的图片加载效率。

如,在 RecyclerView 上下滑动,而RecyclerView中只要是Glide加载过的图片,都可以直接从内存中读取 & 展示,从而不需要重复从 网络或硬盘上读取,提高图片加载效率。

2. Glide 缓存功能介绍

  • Glide 的缓存功能分为:内存缓存 & 磁盘缓存
  • 具体介绍如下

2.1 内存缓存

  • 作用:防止应用 重复将图片数据 读取到内存当中

只 缓存转换过后的图片,而并非原始图片

  • 具体使用 默认情况下,Glide自动开启 内存缓存
  1. // 默认开启内存缓存,用户不需要作任何设置
  2. Glide.with(this)
  3. .load(url)
  4. .into(imageView);

  5. // 可通过 API 禁用 内存缓存功能
  6. Glide.with(this)
  7. .load(url)
  8. .skipMemoryCache(true) // 禁用 内存缓存
  9. .into(imageView);
  • 实现原理 Glide的内存缓存实现是基于:LruCache 算法(Least Recently Used) & 弱引用机制
  1. LruCache算法原理:将 最近使用的对象 用强引用的方式 存储在LinkedHashMap中 ;当缓存满时 ,将最近最少使用的对象从内存中移除
  2. 弱引用:弱引用的对象具备更短生命周期,因为 **当JVM进行垃圾回收时,一旦发现弱引用对象,都会进行回收(无论内存充足否)

2.2 磁盘缓存

  • 作用:防止应用 重复从网络或其他地方重复下载和读取数据

可缓存原始图片 & 缓存转换过后的图片,用户自行设置

  • 具体使用
  1. Glide.with(this)
  2. .load(url)
  3. .diskCacheStrategy(DiskCacheStrategy.NONE)
  4. .into(imageView);

  5. // 缓存参数说明
  6. // DiskCacheStrategy.NONE:不缓存任何图片,即禁用磁盘缓存
  7. // DiskCacheStrategy.ALL :缓存原始图片 & 转换后的图片
  8. // DiskCacheStrategy.SOURCE:只缓存原始图片(原来的全分辨率的图像,即不缓存转换后的图片)
  9. // DiskCacheStrategy.RESULT:(默认)只缓存转换后的图片(即最终的图像:降低分辨率后 / 或者转换后 ,不缓存原始图片
  • 实现原理 使用Glide 自定义的DiskLruCache算法
  1. 该算法基于 Lru 算法中的DiskLruCache算法,具体应用在磁盘缓存的需求场景中
  2. 该算法被封装到Glide自定义的工具类中(该工具类基于Android 提供的DiskLruCache工具类

3. Glide 缓存流程 解析

  • Glide整个缓存流程 从 加载图片请求 开始,其中过程 有本文最关注的 内存缓存的读取 & 写入、磁盘缓存的读取 & 写入
  • 具体如下

示意图

示意图

下面,我将根据 Glide缓存流程中的每个步骤 进行源码分析。


4. 缓存流程 源码分析

步骤1:生成缓存Key

  • Glide 实现内存 & 磁盘缓存 是根据 图片的缓存Key 进行唯一标识

即根据 图片的缓存Key 去缓存区找 对应的缓存图片

  • 生成缓存 Key 的代码发生在Engine类的 load()

#该代码在上一篇文章当中已分析过,只是当时忽略了缓存相关的内容,现在仅贴出缓存相关的代码

  1. public class Engine implements EngineJobListener,
  2. MemoryCache.ResourceRemovedListener,
  3. EngineResource.ResourceListener {

  4. public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
  5. DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
  6. Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
  7. Util.assertMainThread();
  8. long startTime = LogTime.getLogTime();

  9. final String id = fetcher.getId();
  10. // 获得了一个id字符串,即需加载图片的唯一标识
  11. // 如,若图片的来源是网络,那么该id = 这张图片的url地址

  12. EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());
  13. // Glide的缓存Key生成规则复杂:根据10多个参数生成
  14. // 将该id 和 signature、width、height等10个参数一起传入到缓存Key的工厂方法里,最终创建出一个EngineKey对象
  15. // 创建原理:通过重写equals() 和 hashCode(),保证只有传入EngineKey的所有参数都相同情况下才认为是同一个EngineKey对象
  16. // 该EngineKey 即Glide中图片的缓存Key

  17. ...
  18. }

至此,Glide的图片缓存 Key 生成完毕。


步骤2:创建缓存对象 LruResourceCache

  • LruResourceCache对象是在创建 Glide 对象时创建的
  • #而 创建 Glide 对象则是在上篇文章 讲解 Glide 图片加载功能时 第2步load()loadGeneric() 创建 ModelLoader对象时创建的
  • 请看源码分析
  1. <-- 第2步load()中的loadGeneric()-->
  2. private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
  3. ...

  4. ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
  5. // 创建第1个ModelLoader对象;作用:加载图片
  6. // Glide会根据load()方法传入不同类型参数,得到不同的ModelLoader对象
  7. // 此处传入参数是String.class,因此得到的是StreamStringLoader对象(实现了ModelLoader接口)
  8. // Glide.buildStreamModelLoader()分析 ->>分析1


  9. <--分析1:Glide.buildStreamModelLoader() -->
  10. public class Glide {

  11. public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass,
  12. Context context) {
  13. if (modelClass == null) {
  14. if (Log.isLoggable(TAG, Log.DEBUG)) {
  15. Log.d(TAG, "Unable to load null model, setting placeholder only");
  16. }
  17. return null;
  18. }
  19. return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass);
  20. // 创建ModelLoader对象时,调用Glide.get() 创建Glide对象-->分析2
  21. }

  22. <--分析2:Glide.get() -->
  23. // 作用:采用单例模式创建Glide对象
  24. public static Glide get(Context context) {

  25. // 实现单例功能
  26. if (glide == null) {
  27. synchronized (Glide.class) {
  28. if (glide == null) {
  29. Context applicationContext = context.getApplicationContext();
  30. List<GlideModule> modules = new ManifestParser(applicationContext).parse();
  31. GlideBuilder builder = new GlideBuilder(applicationContext);
  32. for (GlideModule module : modules) {
  33. module.applyOptions(applicationContext, builder);
  34. }
  35. glide = builder.createGlide();
  36. // 通过建造者模式创建Glide对象 ->>分析3
  37. for (GlideModule module : modules) {
  38. module.registerComponents(applicationContext, glide);
  39. }
  40. }
  41. }
  42. }
  43. return glide;
  44. }
  45. }

  46. <--分析3:builder.createGlide() -->
  47. // 作用:创建Glide对象
  48. public class GlideBuilder {
  49. ...

  50. Glide createGlide() {
  51. MemorySizeCalculator calculator = new MemorySizeCalculator(context);
  52. if (bitmapPool == null) {
  53. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  54. int size = calculator.getBitmapPoolSize();
  55. bitmapPool = new LruBitmapPool(size);
  56. } else {
  57. bitmapPool = new BitmapPoolAdapter();
  58. }
  59. }

  60. if (memoryCache == null) {
  61. memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
  62. // 创建一个LruResourceCache对象 并 赋值到memoryCache对象
  63. // 该LruResourceCache对象 = Glide实现内存缓存的LruCache对象

  64. }

  65. return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
  66. }
  67. }

至此,创建好了缓存对象LruResourceCache

步骤3:从 内存缓存 中获取缓存图片

  • Glide 在图片加载前就会从 内存缓存 中获取缓存图片
  • 读取内存缓存代码 是在Engine类的load()

即上面讲解的生成缓存 Key 的地方

  • 源码分析
  1. public class Engine implements EngineJobListener,
  2. MemoryCache.ResourceRemovedListener,
  3. EngineResource.ResourceListener {
  4. ...

  5. public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
  6. DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
  7. Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
  8. Util.assertMainThread();

  9. final String id = fetcher.getId();
  10. EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
  11. loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
  12. transcoder, loadProvider.getSourceEncoder());
  13. // 上面讲解的生成图片缓存Key


  14. EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
  15. // 调用loadFromCache()获取内存缓存中的缓存图片

  16. if (cached != null) {
  17. cb.onResourceReady(cached);
  18. }
  19. // 若获取到,就直接调用cb.onResourceReady()进行回调

  20. EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
  21. if (active != null) {
  22. cb.onResourceReady(active);
  23. }
  24. // 若没获取到,就继续调用loadFromActiveResources()获取缓存图片
  25. // 获取到也直接回调

  26. // 若上述两个方法都没有获取到缓存图片,就开启一个新的线程准备加载图片
  27. // 即从上文提到的 Glide最基础功能:图片加载
  28. EngineJob current = jobs.get(key);
  29. return new LoadStatus(cb, current);
  30. }

  31. EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
  32. DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
  33. transcoder, diskCacheProvider, diskCacheStrategy, priority);
  34. EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
  35. jobs.put(key, engineJob);
  36. engineJob.addCallback(cb);
  37. engineJob.start(runnable);

  38. return new LoadStatus(cb, engineJob);
  39. }

  40. ...
  41. }

即:

  • Glide 将 内存缓存 划分为两块:一块使用了LruCache算法 机制;另一块使用了弱引用 机制
  • 当 获取 内存缓存 时,会通过两个方法分别从上述两块区域进行缓存获取
  1. loadFromCache():从 使用了 LruCache算法机制的内存缓存获取 缓存
  2. loadFromActiveResources():从 使用了 弱引用机制的内存缓存获取 缓存

源码分析如下:

  1. // 这2个方法属于 Engine 类
  2. public class Engine implements EngineJobListener,
  3. MemoryCache.ResourceRemovedListener,
  4. EngineResource.ResourceListener {

  5. private final MemoryCache cache;
  6. private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
  7. ...

  8. <-- 方法1:loadFromCache() -->
  9. // 原理:使用了 LruCache算法
  10. private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
  11. if (!isMemoryCacheable) {
  12. return null;
  13. // 若isMemoryCacheable = false就返回null,即内存缓存被禁用
  14. // 即 内存缓存是否禁用的API skipMemoryCache() - 请回看内存缓存的具体使用
  15. // 若设置skipMemoryCache(true),此处的isMemoryCacheable就等于false,最终返回Null,表示内存缓存已被禁用
  16. }

  17. EngineResource<?> cached = getEngineResourceFromCache(key);
  18. // 获取图片缓存 ->>分析4

  19. // 从分析4回来看这里:
  20. if (cached != null) {
  21. cached.acquire();
  22. activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
  23. // 将获取到的缓存图片存储到activeResources当中
  24. // activeResources = 一个弱引用的HashMap:用于缓存正在使用中的图片
  25. // 好处:保护这些图片不会被LruCache算法回收掉。 ->>方法2

  26. }
  27. return cached;
  28. }

  29. <<- 分析4:getEngineResourceFromCache() ->>
  30. // 作用:获取图片缓存
  31. // 具体过程:根据缓存Key 从cache中 取值
  32. // 注:此处的cache对象 = 在构建Glide对象时创建的LruResourceCache对象,即说明使用的是LruCache算法
  33. private EngineResource<?> getEngineResourceFromCache(Key key) {
  34. Resource<?> cached = cache.remove(key);
  35. // 当从LruResourceCache中获取到缓存图片后,会将它从缓存中移除->>回到方法1原处
  36. final EngineResource result;
  37. if (cached == null) {
  38. result = null;
  39. } else if (cached instanceof EngineResource) {
  40. result = (EngineResource) cached;
  41. } else {
  42. result = new EngineResource(cached, true /*isCacheable*/);
  43. }
  44. return result;
  45. }

  46. <-- 方法2:loadFromActiveResources() -->
  47. // 原理:使用了 弱引用机制
  48. // 具体过程:当在方法1中无法获取内存缓存中的缓存图片时,就会从activeResources中取值
  49. // activeResources = 一个弱引用的HashMap:用于缓存正在使用中的图片
  50. private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
  51. if (!isMemoryCacheable) {
  52. return null;
  53. }
  54. EngineResource<?> active = null;
  55. WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
  56. if (activeRef != null) {
  57. active = activeRef.get();
  58. if (active != null) {
  59. active.acquire();
  60. } else {
  61. activeResources.remove(key);
  62. }
  63. }
  64. return active;
  65. }

  66. ...
  67. }

若上述两个方法都没获取到缓存图片时(即内存缓存里没有该图片的缓存),就开启新线程加载图片。


  • 至此,获取内存缓存 的步骤讲解完毕。
  • 总结

示意图

示意图

步骤4:开启 加载图片 线程

  • 若无法从 内存缓存 里 获得缓存的图片,Glide就会开启 加载图片的线程
  • 但在该线程开启后,Glide并不会马上去网络 加载图片,而是采取采用Glide的第2级缓存:磁盘缓存 去获取缓存图片
  1. private Resource<?> decode() throws Exception {

  2. // 在执行 加载图片 线程时(即加载图片时),分两种情况:
  3. // 情况1:从磁盘缓存当中读取图片(默认情况下Glide会优先从缓存当中读取,没有才会去网络源读取图片)
  4. // 情况2:不从磁盘缓存中读取图片

  5. // 情况1:从磁盘缓存中读取缓存图片
  6. if (isDecodingFromCache()) {
  7. // 取决于在使用API时是否开启,若采用DiskCacheStrategy.NONE,即不缓存任何图片,即禁用磁盘缓存
  8. return decodeFromCache();
  9. // 读取磁盘缓存的入口就是这里,此处主要讲解 ->>直接看步骤4的分析9
  10. } else {

  11. // 情况2:不从磁盘缓存中读取图片
  12. // 即上文讨论的从网络读取图片,此处不作过多描述
  13. return decodeFromSource();
  14. }
  15. }

步骤5:从 磁盘缓存 中获取缓存图片

若无法从 内存缓存 里 获得缓存的图片,Glide就会采用第2级缓存:磁盘缓存 去获取缓存图片

  1. <--分析9:decodeFromCache() -->
  2. private Resource<?> decodeFromCache() throws Exception {
  3. Resource<?> result = null;

  4. result = decodeJob.decodeResultFromCache();
  5. // 获取磁盘缓存时,会先获取 转换过后图片 的缓存
  6. // 即在使用磁盘缓存时设置的模式,如果设置成DiskCacheStrategy.RESULT 或DiskCacheStrategy.ALL就会有该缓存
  7. // 下面来分析decodeResultFromCache() ->>分析10

  8. }
  9. if (result == null) {
  10. result = decodeJob.decodeSourceFromCache();
  11. // 如果获取不到 转换过后图片 的缓存,就获取 原始图片 的缓存
  12. // 即在使用磁盘缓存时设置的模式,如果设置成DiskCacheStrategy.SOURCE 或DiskCacheStrategy.ALL就会有该缓存
  13. // 下面来分析decodeSourceFromCache() ->>分析12
  14. }
  15. return result;
  16. }


  17. <--分析10:decodeFromCache() -->
  18. public Resource<Z> decodeResultFromCache() throws Exception {
  19. if (!diskCacheStrategy.cacheResult()) {
  20. return null;
  21. }

  22. Resource<T> transformed = loadFromCache(resultKey);
  23. // 1. 根据完整的缓存Key(由10个参数共同组成,包括width、height等)获取缓存图片
  24. // ->>分析11

  25. Resource<Z> result = transcode(transformed);
  26. return result;
  27. // 2. 直接将获取到的图片 数据解码 并 返回
  28. // 因为图片已经转换过了,所以不需要再作处理
  29. // 回到分析9原处
  30. }


  31. <--分析11:decodeFromCache() -->
  32. private Resource<T> loadFromCache(Key key) throws IOException {
  33. File cacheFile = diskCacheProvider.getDiskCache().get(key);

  34. // 1. 调用getDiskCache()获取Glide自己编写的DiskLruCache工具类实例
  35. // 2. 调用上述实例的get() 并 传入完整的缓存Key,最终得到硬盘缓存的文件

  36. if (cacheFile == null) {
  37. return null;
  38. // 如果文件为空就返回null
  39. }
  40. Resource<T> result = null;
  41. try {
  42. result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
  43. } finally {
  44. if (result == null) {
  45. diskCacheProvider.getDiskCache().delete(key);
  46. }
  47. }
  48. return result;
  49. // 如果文件不为空,则将它解码成Resource对象后返回
  50. // 回到分析10原处
  51. }



  52. <--分析12:decodeFromCache() -->
  53. public Resource<Z> decodeSourceFromCache() throws Exception {
  54. if (!diskCacheStrategy.cacheSource()) {
  55. return null;
  56. }

  57. Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
  58. // 1. 根据缓存Key的OriginalKey来获取缓存图片
  59. // 相比完整的缓存Key,OriginalKey只使用了id和signature两个参数,而忽略了大部分的参数
  60. // 而signature参数大多数情况下用不到,所以基本是由id(也就是图片url)来决定的Original缓存Key
  61. // 关于loadFromCache()同分析11,只是传入的缓存Key不一样

  62. return transformEncodeAndTranscode(decoded);
  63. // 2. 先将图片数据 转换 再 解码,最终返回

  64. }
  • 至此,硬盘缓存读取的源码分析完毕。
  • 总结

示意图

示意图


步骤6:从网络获取 图片资源

  • Glide两级缓存机制里都没有该图片缓存时,只能去源头(如网络)去加载图片了
  • 但从网络加载图片前,需要先获取该图片的网络资源

步骤7:写入 磁盘缓存

  • Glide将图片写入 磁盘缓存的时机:获取图片资源后 、图片加载完成前
  • 写入磁盘缓存又分为:将原始图片 写入 或 将转换后的图片写入磁盘缓存
  • 在第3步 into()中执行图片线程 run()里的 decode()开始(上文的分析13) 此处重新贴出代码
  1. private Resource<?> decode() throws Exception {

  2. // 在执行 加载图片 线程时(即加载图片时),分两种情况:
  3. // 情况1:从磁盘缓存当中读取图片(默认情况下Glide会优先从缓存当中读取,没有才会去网络源读取图片)
  4. // 情况2:不从磁盘缓存中读取图片

  5. // 情况1:从磁盘缓存中读取缓存图片
  6. if (isDecodingFromCache()) {
  7. return decodeFromCache();
  8. // 读取磁盘缓存的入口就是这里,上面已经讲解
  9. } else {

  10. // 情况2:不从磁盘缓存中读取图片
  11. // 即上文讨论的从网络读取图片,不采用缓存
  12. // 写入磁盘缓存就是在 此处 写入的 ->>分析13
  13. return decodeFromSource();
  14. }
  15. }


  16. <--分析13:decodeFromSource() -->
  17. public Resource<Z> decodeFromSource() throws Exception {
  18. Resource<T> decoded = decodeSource();
  19. // 解析图片
  20. // 写入原始图片 磁盘缓存的入口 ->>分析14

  21. // 从分析16回来看这里
  22. return transformEncodeAndTranscode(decoded);
  23. // 对图片进行转码
  24. // 写入 转换后图片 磁盘缓存的入口 ->>分析17
  25. }


  26. <--分析14:decodeSource() -->
  27. private Resource<T> decodeSource() throws Exception {
  28. Resource<T> decoded = null;
  29. try {

  30. final A data = fetcher.loadData(priority);
  31. // 读取图片数据
  32. if (isCancelled) {
  33. return null;
  34. }
  35. decoded = decodeFromSourceData(data);
  36. // 对图片进行解码 ->>分析15
  37. } finally {
  38. fetcher.cleanup();
  39. }
  40. return decoded;
  41. }

  42. <--分析15:decodeFromSourceData() -->
  43. private Resource<T> decodeFromSourceData(A data) throws IOException {
  44. final Resource<T> decoded;
  45. // 判断是否允许缓存原始图片
  46. // 即在使用 硬盘缓存API时,是否采用DiskCacheStrategy.ALL 或 DiskCacheStrategy.SOURCE
  47. if (diskCacheStrategy.cacheSource()) {
  48. decoded = cacheAndDecodeSourceData(data);
  49. // 若允许缓存原始图片,则调用cacheAndDecodeSourceData()进行原始图片的缓存 ->>分析16

  50. } else {
  51. long startTime = LogTime.getLogTime();
  52. decoded = loadProvider.getSourceDecoder().decode(data, width, height);
  53. }
  54. return decoded;
  55. }

  56. <--分析16:cacheAndDecodeSourceData -->
  57. private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {

  58. ...
  59. diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
  60. // 1. 调用getDiskCache()获取DiskLruCache实例
  61. // 2. 调用put()写入硬盘缓存
  62. // 注:原始图片的缓存Key是用的getOriginalKey(),即只有id & signature两个参数
  63. // 请回到分析13

  64. }

  65. <--分析17:transformEncodeAndTranscode() -->
  66. private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {

  67. Resource<T> transformed = transform(decoded);
  68. // 1. 对图片进行转换

  69. writeTransformedToCache(transformed);
  70. // 2. 将 转换过后的图片 写入到硬盘缓存中 -->分析18

  71. Resource<Z> result = transcode(transformed);
  72. return result;
  73. }

  74. <-- 分析18:TransformedToCache() -->
  75. private void writeTransformedToCache(Resource<T> transformed) {
  76. if (transformed == null || !diskCacheStrategy.cacheResult()) {
  77. return;
  78. }

  79. diskCacheProvider.getDiskCache().put(resultKey, writer);
  80. // 1. 调用getDiskCache()获取DiskLruCache实例
  81. // 2. 调用put()写入硬盘缓存
  82. // 注:转换后图片的缓存Key是用的完整的resultKey,即含10多个参数
  83. }
  • 至此,硬盘缓存的写入分析完毕。
  • 总结

示意图

示意图


步骤9:写入 内存缓存

  • Glide 将图片写入 内存缓存的时机:图片加载完成后 、图片显示出来前
  • 写入 内存缓存 的具体地方:上篇文章中当图片加载完成后,会在EngineJob中通过Handler发送一条消息将执行逻辑切回到主线程当中,从而执行handleResultOnMainThread()
  1. class EngineJob implements EngineRunnable.EngineRunnableManager {

  2. private final EngineResourceFactory engineResourceFactory;
  3. ...

  4. private void handleResultOnMainThread() {
  5. ...

  6. // 关注1:写入 弱引用缓存
  7. engineResource = engineResourceFactory.build(resource, isCacheable);
  8. listener.onEngineJobComplete(key, engineResource);

  9. // 关注2:写入 LruCache算法的缓存
  10. engineResource.acquire();

  11. for (ResourceCallback cb : cbs) {
  12. if (!isInIgnoredCallbacks(cb)) {
  13. engineResource.acquire();
  14. cb.onResourceReady(engineResource);
  15. }
  16. }
  17. engineResource.release();
  18. }

写入 内存缓存分为:写入 弱引用缓存 & LruCache算法的缓存

  1. 内存缓存分为:一块使用了 LruCache算法机制的区域 & 一块使用了 弱引用机制的缓存
  2. 内存缓存只缓存 转换后的图片

关注1:写入 弱引用缓存

  1. class EngineJob implements EngineRunnable.EngineRunnableManager {

  2. private final EngineResourceFactory engineResourceFactory;
  3. ...

  4. private void handleResultOnMainThread() {
  5. ...

  6. // 写入 弱引用缓存
  7. engineResource = engineResourceFactory.build(resource, isCacheable);
  8. // 创建一个包含图片资源resource的EngineResource对象

  9. listener.onEngineJobComplete(key, engineResource);
  10. // 将上述创建的EngineResource对象传入到Engine.onEngineJobComplete() ->>分析6


  11. // 写入LruCache算法的缓存(先忽略)
  12. engineResource.acquire();

  13. for (ResourceCallback cb : cbs) {
  14. if (!isInIgnoredCallbacks(cb)) {
  15. engineResource.acquire();
  16. cb.onResourceReady(engineResource);
  17. }
  18. }
  19. engineResource.release();
  20. }


  21. <<- 分析6:onEngineJobComplete()() ->>
  22. public class Engine implements EngineJobListener,
  23. MemoryCache.ResourceRemovedListener,
  24. EngineResource.ResourceListener {
  25. ...

  26. @Override
  27. public void onEngineJobComplete(Key key, EngineResource<?> resource) {
  28. Util.assertMainThread();

  29. if (resource != null) {
  30. resource.setResourceListener(key, this);
  31. if (resource.isCacheable()) {
  32. activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
  33. // 将 传进来的EngineResource对象 添加到activeResources()中
  34. // 即写入了弱引用 内存缓存
  35. }
  36. }
  37. jobs.remove(key);
  38. }

  39. ...
  40. }

关注2:写入 LruCache算法 缓存

  1. class EngineJob implements EngineRunnable.EngineRunnableManager {

  2. private final EngineResourceFactory engineResourceFactory;
  3. ...

  4. private void handleResultOnMainThread() {
  5. ...

  6. // 写入 弱引用缓存(忽略)
  7. engineResource = engineResourceFactory.build(resource, isCacheable);
  8. listener.onEngineJobComplete(key, engineResource);

  9. // 写入 LruCache算法的缓存
  10. engineResource.acquire();
  11. // 标记1

  12. for (ResourceCallback cb : cbs) {
  13. if (!isInIgnoredCallbacks(cb)) {
  14. engineResource.acquire();
  15. // 标记2
  16. cb.onResourceReady(engineResource);
  17. }
  18. }
  19. engineResource.release();
  20. // 标记3
  21. }

写入 LruCache算法 内存缓存的原理:包含图片资源resourceEngineResource对象的一个引用机制:

  • 用 一个 acquired 变量 记录图片被引用的次数
  • 加载图片时:调用 acquire() ,变量加1

上述代码的标记1、标记2 & 下面acquire()源码

  1. <-- 分析7:acquire() -->
  2. void acquire() {
  3. if (isRecycled) {
  4. throw new IllegalStateException("Cannot acquire a recycled resource");
  5. }
  6. if (!Looper.getMainLooper().equals(Looper.myLooper())) {
  7. throw new IllegalThreadStateException("Must call acquire on the main thread");
  8. }
  9. ++acquired;
  10. // 当调用acquire()时,acquired变量 +1
  11. }
  • 不加载图片时,调用 release() 时,变量减1

上述代码的标记3 & 下面release()源码

  1. <-- 分析8:release() -->
  2. void release() {
  3. if (acquired <= 0) {
  4. throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
  5. }
  6. if (!Looper.getMainLooper().equals(Looper.myLooper())) {
  7. throw new IllegalThreadStateException("Must call release on the main thread");
  8. }
  9. if (--acquired == 0) {
  10. listener.onResourceReleased(key, this);
  11. // 当调用acquire()时,acquired变量 -1
  12. // 若acquired变量 = 0,即说明图片已经不再被使用
  13. // 调用listener.onResourceReleased()释放资源
  14. // 该listener = Engine对象,Engine.onResourceReleased()->>分析9
  15. }
  16. }
  17. }

  18. <-- 分析9:onResourceReleased() -->

  19. public class Engine implements EngineJobListener,
  20. MemoryCache.ResourceRemovedListener,
  21. EngineResource.ResourceListener {

  22. private final MemoryCache cache;
  23. private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
  24. ...

  25. @Override
  26. public void onResourceReleased(Key cacheKey, EngineResource resource) {
  27. Util.assertMainThread();
  28. activeResources.remove(cacheKey);
  29. // 步骤1:将缓存图片从activeResources弱引用缓存中移除

  30. if (resource.isCacheable()) {
  31. cache.put(cacheKey, resource);
  32. // 步骤2:将该图片缓存放在LruResourceCache缓存中
  33. } else {
  34. resourceRecycler.recycle(resource);
  35. }
  36. }

  37. ...
  38. }

所以:

  • acquired 变量 >0 时,说明图片正在使用,即该图片缓存继续存放到activeResources弱引用缓存中
  • acquired变量 = 0,即说明图片已经不再被使用,就将该图片的缓存Key从 activeResources弱引用缓存中移除,并存放到LruResourceCache缓存中

至此,实现了:

  • 正在使用中的图片 采用 弱引用 的内存缓存
  • 不在使用中的图片 采用 LruCache算法 的内存缓存

总结

示意图

示意图


步骤10:显示图片

  • 在将图片 写入 内存缓存 & 磁盘缓存后,图片最终显示出来
  • 在下次加载时,将通过二级缓存 从而提高图片加载效率

至此,Glide 的图片缓存流程解析完毕。

示意图

示意图

  • 关于内存缓存 的总结
    1. 读取 内存缓存 时,先从LruCache算法机制的内存缓存读取,再从弱引用机制的 内存缓存 读取
    2. 写入 内存缓存 时,先写入 弱引用机制 的内存缓存,等到图片不再被使用时,再写入到 LruCache算法机制的内存缓存
  • 关于磁盘缓存 的总结
    1. 读取 磁盘缓存 时,先读取 转换后图片 的缓存,再读取 原始图片 的缓存

是否读取 取决于 Glide使用API的设置

  1. 写入 磁盘缓存 时,先写入 原始图片 的内存缓存,再写入的内存缓存

是否写入 取决于 Glide使用API的设置

参考链接:https://juejin.cn/post/6844903583280791559