Glide 系列六:Glide 三级缓存

1,007 阅读5分钟

Glide 活动缓存

Glide 活动缓存的设计思想

  • 从业务场景出发

    • 设计了内存缓存(LRUcache ),maxSize = 3;当第四张图片进入时,内存缓存就会移除(直接释放掉这张图片的Bitmap )一张,但是,如果说这张图片正在使用,那么就会奔溃
  • 怎么解决崩溃问题:设计了活动缓存

    • 活动缓存设计成:弱引用集合,不再用LRU 算法了

      • 正在使用的图片,必须放到活动缓存中,不管你有多少张;是正在使用,不是使用过;
      • 资源是在两级缓存中反复横跳;
    • 执行逻辑:先加载活动缓存,发现没有我要的图片;那么,加载内存缓存,发现有;此时,就将内存缓存中的这张图片,移动到活动缓存中;进行显示;

    • 如果有一天,用户关闭掉了这个Activity,那么我再将活动缓存的这张图片放回内存缓存;

    • 此时,又来一个Activity 想要去加载图片,此时还是先去活动缓存中找,没有就去内存缓存中找,有了,将这张图片从内存缓存放到活动缓存,显示图片;

      • 只要你关了Activity ,因为是弱引用,且有生命周期,那么放回去就行了;
  • 设计细节:

    • 资源引用计数:Glide 是可以知道资源是否被使用的;

      • 当资源被使用,那么计数加一;引用链断掉,计数减一;
      • 当这个计数等于 0 ,将资源从活动缓存放到内存资源中;
    • LRU 容量:自定义的,

      • 以前(3.x ):Glide 设置其为当前运行内存的八分之一;
      • 现在:计算手机当前剩余内存,得到一个访问,再去设置;
    • GC 细节:

      • 不需要考虑活动缓存的回收,存在引用链的;
    • 访问方式:

      • 我是通过key 去找的资源,至于资源放在那里,不管;
      • 图片的key 一定是唯一的;
    • android 图片细节:避免崩溃

      • 图片并不是整张展示的,看多少,给多少;
      • 即使原文件是超级高清的,但是android 系统会优化,跟微信一样;但是这个是可以改的,有个选项;
    • 活动缓存是没有上线的:

      • 活动缓存的设计初心就是去解决内存缓存(LRUcache )的上限问题;
      • 这个底层是HashMap
    • 磁盘缓存能不能替代活动缓存?

      • 不能,磁盘缓存是持久化存储,访问是通过I/O 流的;这个访问速度肯定是不及活动缓存的(内存);
  • 资源在两级缓存中是拷贝还是移动?

    • 移动,不能存在两份的;
  • 细节:

    • 这两个缓存都是在内存中的,属于运行时内存
    • 内存缓存:可以叫LRUcache、二级缓存
    • 活动缓存:可以叫活动资源、活跃资源、前台缓存等等

LRUcache 设计思想

  • 最少使用算法:最近使用的最难回收,

  • 设计思路:

    • maxSize:LRUcache 的最大容量

    • put:

      • 没有满就继续放,但是需要维护访问顺序
      • 放满了,将最久没访问的,删除掉
    • get :

      • 拿东西出来,注意维护访问顺序
    • 自定义数据结构:使用双向链表

    • 底层原理:LinkedHashMap

      • 里面存在访问排序,
  • 关键代码:

     class LRUCache {
         class Node {
             int k, v;
             Node l, r;
             Node(int _k, int _v) {
                 k = _k;
                 v = _v;
             }
         }
         int n;
         Node head, tail;
         Map<Integer, Node> map;
         public LRUCache(int capacity) {
             n = capacity;
             map = new HashMap<>();
             head = new Node(-1, -1);
             tail = new Node(-1, -1);
             head.r = tail;
             tail.l = head;
         }
         
         public int get(int key) {
             if (map.containsKey(key)) {
                 Node node = map.get(key);
                 refresh(node);
                 return node.v;
             } 
             return -1;
         }
         
         public void put(int key, int value) {
             Node node = null;
             if (map.containsKey(key)) {
                 node = map.get(key);
                 node.v = value;
             } else {
                 if (map.size() == n) {
                     Node del = tail.l;
                     map.remove(del.k);
                     delete(del);
                 }
                 node = new Node(key, value);
                 map.put(key, node);
             }
             refresh(node);
         }
         
         // refresh 操作分两步:
         // 1. 先将当前节点从双向链表中删除(如果该节点本身存在于双向链表中的话)
         // 2. 将当前节点添加到双向链表头部
         void refresh(Node node) {
             delete(node);
             node.r = head.r;
             node.l = head;
             head.r.l = node;
             head.r = node;
         }
         
         // delete 操作:将当前节点从双向链表中移除
         // 由于我们预先建立 head 和 tail 两位哨兵,因此如果 node.l 不为空,则代表了 node 本身存在于双向链表(不是新节点)
         void delete(Node node) {
             if (node.l != null) {
                 Node left = node.l;
                 left.r = node.r;
                 node.r.l = left;
             }
         }
     }
    

Glide 缓存机制加载流程

业务流程:缓存命中流程

  • 优先寻找活动缓存:

    • 命中,显示图片,流程结束;
    • 找不到向下寻找,进入内存缓存
  • 进入内存缓存:

    • 命中,将对应资源移动至活动缓存,显示图片,流程结束;
    • 找不到向下,进入磁盘缓存
  • 进入LRU 磁盘缓存:

    • 命中,将对应资源复制一份至活动缓存,显示图片,流程结束;

      • 磁盘缓存,没有remove 操作的
    • 找不到向下,进入模型层(网络、本地)

  • 进入模型层:网络资源、本地文件

    • 命中,将对应资源移动至磁盘缓存,继续向上至活动缓存,显示图片,流程结束;

业务流程:引入生命周期

  • 当MainActivity 触发onDestroy() 时,活动缓存释放,将资源放入LRU内存缓存

LRU 算法

应用场景:

  • Glide 缓存中的磁盘缓存与内存缓存的缓存策略

设计思想:

  • 移除最久未使用的元素

  • 细节:使用也就是访问

  • 设计细节:

    • maxSize:LRUcache 的最大容量
  • 数据结构:双向链表

底层原理:LruCache

  • 在android.util 下面就有这个东西;

  • 实现原理:封装了LinkedHashMap

  • 代码细节: LruCache 构造函数中

     //这个true 就是自动排序
     this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    
  • put 细节:

    • 元素个数大于maxSize ,移除最久没有使用的元素;
    • 插入重复元素,移除之前的元素

执行逻辑:

  • put:

    • 没有满就继续放,但是需要维护访问顺序
    • 放满了,将最久没访问的,删除掉
  • get :

    • 拿东西出来,注意维护访问顺序
  • 自定义数据结构:使用双向链表

  • 底层原理:LinkedHashMap

    • 里面存在访问排序,

活动/内存的由来

为什么有了内存缓存,还要去设计活动缓存

  • 从业务场景出发:LRUcache 自身的限制

    • 只设计内存缓存(LRUcache ),maxSize = 3;当第四张图片进入时,内存缓存就会移除(直接释放掉这张图片的Bitmap )一张,但是,如果说这张图片正在使用,那么就会奔溃
  • 怎么解决崩溃问题:设计了活动缓存

    • 活动缓存设计成:弱引用集合,不再用LRU 算法

      • 正在使用的图片,必须放到活动缓存中,不管你有多少张;是正在使用,不是使用过;
      • 资源是在两级缓存中反复横跳;
      • 活动缓存跟随APP,LRUcache 不会跟随APP
    • 执行逻辑:先加载活动缓存,发现没有我要的图片;那么,加载内存缓存,发现有;此时,就将内存缓存中的这张图片,移动到活动缓存中;进行显示;

    • 如果有一天,用户关闭掉了这个Activity,那么我再将活动缓存的这张图片放回内存缓存;

    • 此时,又来一个Activity 想要去加载图片,此时还是先去活动缓存中找,没有就去内存缓存中找,有了,将这张图片从内存缓存放到活动缓存,显示图片;

      • 只要你关了Activity ,因为是弱引用,且有生命周期,那么放回去就行了;