Android的三级缓存,其中主要的就是内存缓存和硬盘缓存。这两种缓存机制的实现都应用到了LruCache算法,下面我们就从使用到源码解析,来彻底理解Android中的缓存机制。
LruCache 介绍
LruCache 顾名思义就是使用LRU缓存策略的缓存,那么LRU是什么呢? 最近最少使用到的(least recently used),就是当超出缓存容量的时候,就优先淘汰链表中最近最少使用的那个数据。
讲到LruCache,其实最关键的还是LinkedHashMap。LruCache的源码本身很少,而实现了LRU的功能都是靠LinkedHashMap。
LruCache 使用
// 初始化LruCache
int memory = (int) Runtime.getRuntime().totalMemory() / 1024;
int cache = memory / 8;
LruCache lruCache = new android.util.LruCache<String, Bitmap>(cache) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
// 存
lruCache.put("key", value);
// 取
lruCache.get("key");
LruCache 初始化
LruCache初始化,主要初始化一个 LinkedHashMap ,可以看到比平常我们初始化多一个 accessOrder = true 的过程
accessOrder = true 表示: LinkedHashMap 是按操作顺序排序的,put, get 操作都会把当前元素方法队尾
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
LruCache 存元素
public final V put(@NonNull K key, @NonNull V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
// 把当前消耗的size加上当前value的size,safeSizeOf 其实就是继承的 sizeOf 方法计算得到的内存大小
size += safeSizeOf(key, value);
// 把元素加入到 LinkedHashMap 当中,返回值 previous 表示当前key值,上次的value值
previous = map.put(key, value);
// 如果不为空,把上一次的空间减去
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
// 告知元素被移除了,可以自己在这里实现具体移除该做的操作
entryRemoved(false, key, previous, value);
}
// 判断空间有没有超,超出的话,移除队尾最不常使用的元素
trimToSize(maxSize);
return previous;
}
// 就是为了防止计算的内存小于0出错,其实就是计算内存的
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
// 如果要计算真实内存的size,必须自己继承计算
protected int sizeOf(@NonNull K key, @NonNull V value) {
return 1;
}
public void trimToSize(int maxSize) {
// 不断循环,知道空间够了为止
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize || map.isEmpty()) {
break;
}
// 取队列第一个元素移除
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
LruCache 取元素
public final V get(@NonNull K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
// 从 LinkedHashMap 中取当前key的元素
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
// 如果有直接返回
return mapValue;
}
missCount++;
}
// 如果没有,尝试通过 create 方法恢复元素,需要自己继承实现 create 方法
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
// create 方法恢复出来,放入 LinkedHashMap 当中
mapValue = map.put(key, createdValue);
// 后续方法和put方法类似
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
为什么 LinkedHashMap 的 get 方法可以把元素方法队尾
可以看到,当 accessOrder = true 的时候,就多走了一个方法——afterNodeAccess
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
afterNodeAccess 方法,其实就是把元素 e 方法队尾。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
LruCache 注意事项
多个继承方法
如果要计算真实内存的size,必须自己继承计算内存size的方法 sizeOf ,不然默认每个对象为 size + 1,不会计算真实内存大小。
如果想某个key的value被回收之后不为空,可以继续使用,必须继承 create 方法,自己在当前key获得的value为空之后,重新创建一个。
如果想要知道某个元素被移除了,可以继承 entryRemoved 方法。
找对正确的 LruCache
我看得到是android Q (29) 的源码
android.util.LruCache 中的 trimToSize() 方法,去掉的是队尾。开始我也被搞蒙了很久,LinkedHashMap 不是最近的放在队尾的吗?
后面找了一下,还有一个 android.collection.LruCache ,人家取的就是队列第一个元素。没搞明白是不是 android.util.LruCache 里面错了。
反正后面也找了其他地方得 LruCache ,比如Gilde等,清一色都是去掉的队列第一个元素。
看网上以前android版本的 android.util.LruCache 删的也是第一个元素,不知道什么版本开始删的队尾,有兴趣的可以自己找下。
Map.Entry<K, V> toEvict = null;
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
// END LAYOUTLIB CHANGE
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
android.collection.LruCache 中的 trimToSize() 方法,去掉的是队列第一个元素
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
————————————————
版权声明:本文为CSDN博主「吴中乐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_16927853/article/details/102905637