首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Android Glide 缓存机制及源码全解

  • 25-03-02 15:02
  • 3681
  • 7218
blog.csdn.net

目录

Glide里的缓存

什么是三级缓存?

缓存机制

为何设计出活动缓存

加载流程

Glide源码

加载流程

LRU是什么

内存缓存的LRU

LruCache

往期回顾 

RecyclerView 绘制流程及Recycler缓存

Glide使用详解


Glide里的缓存

默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:

  • 活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?

  • 内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?

  • 资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?

  • 数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?

        前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。

        如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。

什么是三级缓存?

  • 内存缓存:优先加载,速度最快

  • 本地缓存:其次加载,速度快

  • 网络缓存:最后加载,速度慢,浪费流量

缓存机制

        Glide使用了ActiveResources(活动缓存弱引用)+MemoryCache(内存缓存Lru算法)+DiskCache(磁盘缓存Lru算法)。

  • ActiveResources:存储当前界面使用到的图片。界面不展示后,该Bitmap又被缓存至MemoryCache中,并从ActiveResources中删除。

  • Memory Cache:存储当前没有使用到的Bitmap,当MemoryCache中得到Bitmap后,该Bitmap又被缓存至ActiveResources中,并从MemoryCache中删除。

  • Disk Cache:持久缓存。例如图片加圆角,处理后图片会被缓存到文件中,应用被再次打开时可以加载缓存直接使用。

        注意: ActiveResources + MemoryCache是内存缓存,都属于运行时缓存,且互斥(同一张图片不会同时缓存在ActiveResources+MemoryCache),应用被杀死后将不存在。

        Glide 内部是使用 LruCache、弱引用和硬盘缓存实现的。

        Glide 主要将缓存分为两块内存缓存和硬盘缓存,两种缓存的结合,构成了 Glide 缓存机制的核心。

为何设计出活动缓存

        因为内存缓存使用LRU算法,当你使用Gilde加载并显示第一张图片时,后面又加载了很多图片,同时你的第一张图片还在用。这个时候内存缓存根据LRU算法可能会删除你正在使用的第一张照片。这样的后果就是你正在使用的照片找不到,后果就是程序崩溃。

加载流程

        流程就是这么个流程下面咱们通过源码加深一下。

Glide源码

加载流程

        1.Engine类

        负责启动加载并管理活动资源和缓存资源,它里面有个load方法。没错就是提供路径加载图片的方法。

        2.load方法

        这个方法里面满满的干货。

  1. public  LoadStatus load(...) {
  2.     long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
  3.     EngineKey key =
  4.         keyFactory.buildKey(
  5.             model,
  6.             signature,
  7.             width,
  8.             height,
  9.             transformations,
  10.             resourceClass,
  11.             transcodeClass,
  12.             options);
  13.     EngineResource memoryResource;
  14.     synchronized (this) {
  15.       memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
  16.       if (memoryResource == null) {
  17.         return waitForExistingOrStartNewJob(...);
  18.       }
  19.     }
  20.     // Avoid calling back while holding the engine lock, doing so makes it easier for callers to
  21.     // deadlock.
  22.     cb.onResourceReady(
  23.         memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
  24.     return null;
  25.   }

        3.EngineKey

        An in memory only cache key used to multiplex loads.

        用于多路传输加载的仅内存缓存密钥.

  1.  EngineKey key =
  2.         keyFactory.buildKey(
  3.             ...);

        4.loadFromMemory

        根据上面load方法提供咱们来看看loadFromMemory()这个是重点;

        5.loadFromActiveResources

        6.loadFromCache

        7.getEngineResourceFromCache

        到这里如有还未找到,那就说明该图片未保存至内存缓存中来。咱继续往下走,顺着源码跑。

        8.waitForExistingOrStartNewJob

        咱弄个简化版

  1. private  LoadStatus waitForExistingOrStartNewJob(...) {
  2.     //通过添加和删除加载的回调并通知来管理加载的类
  3.     //加载完成时回调。
  4.     //咱都没数据肯定没加载完成,这个不管。急着往下看
  5.     EngineJob current = jobs.get(key, onlyRetrieveFromCache);
  6.     if (current != null) {
  7.       current.addCallback(cb, callbackExecutor);
  8.       if (VERBOSE_IS_LOGGABLE) {
  9.         logWithTimeAndKey("Added to existing load", startTime, key);
  10.       }
  11.       return new LoadStatus(cb, current);
  12.     }
  13.     //同上,接着向下看
  14.     EngineJob engineJob =
  15.         engineJobFactory.build(
  16.             key,
  17.             isMemoryCacheable,
  18.             useUnlimitedSourceExecutorPool,
  19.             useAnimationPool,
  20.             onlyRetrieveFromCache);
  21.     //负责从缓存数据或原始源解码资源的类,看着像,咱看看DecodeJob
  22.     //应用转换和代码转换。
  23.     DecodeJob decodeJob =
  24.         decodeJobFactory.build(
  25.             ...
  26.             engineJob);
  27.     jobs.put(key, engineJob);
  28.     engineJob.addCallback(cb, callbackExecutor);
  29.     engineJob.start(decodeJob);
  30.     if (VERBOSE_IS_LOGGABLE) {
  31.       logWithTimeAndKey("Started new load", startTime, key);
  32.     }
  33.     return new LoadStatus(cb, engineJob);
  34.   }

        9.DecodeJob

  1. class DecodeJob
  2.     implements DataFetcherGenerator.FetcherReadyCallback,
  3.         Runnable,
  4.         Comparable>,
  5.         Poolable {
  6.   }
  7.   ...
  8.   //构造方法有个DiskCacheProvider看着跟磁盘缓存有关咱进去瞅瞅
  9.   DecodeJob(DiskCacheProvider diskCacheProvider, Pools.Pool> pool) {
  10.     this.diskCacheProvider = diskCacheProvider;
  11.     this.pool = pool;
  12.   }
  13.   ...

        10.DiskCacheProvider

        磁盘缓存实现的入口。

        在指定的内存中创建基于{@link com.bumptech.glide.disklrucache.disklrucache}的磁盘缓存。

        磁盘缓存目录。

  1. public class DiskLruCacheFactory implements DiskCache.Factory {
  2.   private final long diskCacheSize;
  3.   private final CacheDirectoryGetter cacheDirectoryGetter;
  4.   /** 在UI线程外调用接口以获取缓存文件夹。 */
  5.   public interface CacheDirectoryGetter {
  6.     File getCacheDirectory();
  7.   }
  8.   public DiskLruCacheFactory(final String diskCacheFolder, long diskCacheSize) {
  9.     this(
  10.         new CacheDirectoryGetter() {
  11.           @Override
  12.           public File getCacheDirectory() {
  13.             return new File(diskCacheFolder);
  14.           }
  15.         },
  16.         diskCacheSize);
  17.   }
  18.   public DiskLruCacheFactory(
  19.       final String diskCacheFolder, final String diskCacheName, long diskCacheSize) {
  20.     this(
  21.         new CacheDirectoryGetter() {
  22.           @Override
  23.           public File getCacheDirectory() {
  24.             return new File(diskCacheFolder, diskCacheName);
  25.           }
  26.         },
  27.         diskCacheSize);
  28.   }
  29. /**
  30. *使用此构造函数时,将调用{@link CacheDirectoryGetter#getCacheDirectory()}
  31. *UI线程,允许在不影响性能的情况下进行I/O访问。
  32. *在UI线程外调用@param cacheDirectoryGetter接口以获取缓存文件夹。
  33. *@param diskCacheSize LRU磁盘缓存所需的最大字节大小。
  34. */
  35.   // Public API.
  36.   @SuppressWarnings("WeakerAccess")
  37.   public DiskLruCacheFactory(CacheDirectoryGetter cacheDirectoryGetter, long diskCacheSize) {
  38.     this.diskCacheSize = diskCacheSize;
  39.     this.cacheDirectoryGetter = cacheDirectoryGetter;
  40.   }
  41.   @Override
  42.   public DiskCache build() {
  43.     File cacheDir = cacheDirectoryGetter.getCacheDirectory();
  44.     if (cacheDir == null) {
  45.       return null;
  46.     }
  47.     if (cacheDir.isDirectory() || cacheDir.mkdirs()) {
  48.       return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
  49.     }
  50.     return null;
  51.   }
  52. }

        11.DiskCache.Factory

        DiskLruCacheFactory实现的接口是什么,咱看看

  1. /** 用于向磁盘缓存写入数据和从磁盘缓存读取数据的接口 */
  2. public interface DiskCache {
  3.   /** 用于创建磁盘缓存的接口 */
  4.   interface Factory {
  5.     /** 250 MB of cache. */
  6.     int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
  7.     String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
  8.     /** 返回新的磁盘缓存,如果无法创建磁盘缓存,则返回{@code null}*/
  9.     @Nullable
  10.     DiskCache build();
  11.   }
  12.   /** 向磁盘缓存中的密钥实际写入数据的接口 */
  13.   interface Writer {
  14.   /**
  15.   *将数据写入文件
  16.   *如果写入操作应中止,则返回false。
  17.   *@param file写入程序应写入的文件。
  18.   */
  19.     boolean write(@NonNull File file);
  20.   }
  21.   /**
  22.    *获取给定键处的值的缓存。
  23.    */
  24.   @Nullable
  25.   File get(Key key);
  26.   /**
  27.   *@param key要写入的密钥。
  28.   *@param writer一个接口,该接口将在给定密钥输出流的情况下写入数据。
  29.    */
  30.   void put(Key key, Writer writer);
  31.   /**
  32.    * 从缓存中删除键和值。.
  33.    */
  34.   @SuppressWarnings("unused")
  35.   void delete(Key key);
  36.   /** Clear the cache. */
  37.   void clear();
  38. }

        磁盘缓存写入和读取的接口有了,那其他相关联的源码找到试着理解也是没问题的,再多找就乱了。

LRU是什么

        LRU是近期最少使用的算法(缓存淘汰算法),它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LrhCache和DisLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。

        LruCache的核心思想很好理解,就是要维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。

内存缓存的LRU

  1. /** An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s. */
  2. public class LruResourceCache extends LruCache> implements MemoryCache {
  3.   private ResourceRemovedListener listener;
  4.   /**
  5.   *LruResourceCache的构造函数。
  6.   *@param size内存缓存可以使用的最大字节大小。
  7.   */
  8.   public LruResourceCache(long size) {
  9.     super(size);
  10.   }
  11.   @Override
  12.   public void setResourceRemovedListener(@NonNull ResourceRemovedListener listener) {
  13.     this.listener = listener;
  14.   }
  15.   @Override
  16.   protected void onItemEvicted(@NonNull Key key, @Nullable Resource item) {
  17.     if (listener != null && item != null) {
  18.       listener.onResourceRemoved(item);
  19.     }
  20.   }
  21.   @Override
  22.   protected int getSize(@Nullable Resource item) {
  23.     if (item == null) {
  24.       return super.getSize(null);
  25.     } else {
  26.       return item.getSize();
  27.     }
  28.   }
  29.   @SuppressLint("InlinedApi")
  30.   @Override
  31.   public void trimMemory(int level) {
  32.     if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
  33.      //正在输入缓存的后台应用程序列表
  34.     //退出我们的整个Bitmap缓存
  35.       clearMemory();
  36.     } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
  37.         || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
  38.       // The app's UI is no longer visible, or app is in the foreground but system is running
  39.       // critically low on memory
  40.       // Evict oldest half of our bitmap cache
  41.       trimToSize(getMaxSize() / 2);
  42.     }
  43.   }
  44. }

LruCache

        存在一个LinkedHashMap存放数据,并且实现了LRU(最少使用算法)缓存策略。

Map cache = new LinkedHashMap<>(100,0.75f, true):
  • 其中第二个参数0.75f表示加载因子,即容量达到75%的时候会把内存临时增加一倍。

  • 最后这个参数也至关重要,表示访问元素的排序方式,true表示按照访问顺序排序,false表示按败插入的顺序排序。

LruCache实现原理

        利用了LinkedHashMap排序方式的特性:由于使用访问顺序排序,进行了get/put操作的元素会放在Map最后面。所以当最后一个元素插入进来时,如果当前的缓存数据大小超过了最大限制,那么会删除Map中放在前面的元素。

往期回顾 

RecyclerView 绘制流程及Recycler缓存

Glide使用详解

注:本文转载自blog.csdn.net的帅次的文章"https://shuaici.blog.csdn.net/article/details/120015782"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top