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 ,因为是弱引用,且有生命周期,那么放回去就行了;
-