LruCache源码解析
在Android中一个应用程序占用的内存是有限的,而Android中通常需要加载很多图片显示给用户,图片是很消耗内存的,稍微处理不好就很容易报OOM异常。而且无论图片是从本地还是网络获取,解析并加载进内存都是一个耗时的过程,所以我们通常就会把一些常用的图片保存在内存中,在使用的时候直接从内存中获取,从而提高程序的响应性和用户体验。在保存图片到内存中就会用到LruCache这个类,今天我们就看看这个类的简单使用和内部实现原理。
1. LruCache简单使用
// 以在内存中保存图片为例:
int cacheSize = (int) (Runtime.getRuntime().maxMemory()/8); // 获取最大内存的1/8用来保存图片
LruCache<String,Bitmap> lruCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount(); // 这个方法必须复写,告诉程序每次存入对象所占内存的大小,默认为1
}
};
// 使用的时候直接调用put(String,Bitmap),get(String)和remove(String)方法就可以直接存、取、移除对象了。它会在每次存入对象的时候,自动检测是否超过设置的总内存大小,如果没有,就直接存入,如果超过设置的最大内存,就会移除掉最先存入最不常用的对象。
2. LruCache源码解析
构造方法
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对象的时候,在构造方法中会创建一个初始容量为0,加载因子为0.75 ,顺序访问(调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表)的LinkedHashMap容器来保持对象,同时设置LruCache保存的对象最大能够占用的内存大小。
get方法
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
// 上面这段代码,首先会根据传入的key在linkedHashmap中去取对应的对象,如果返回的对象不为null,那么就直接返回,否则就会走下面这段代码调用create(String)方法获取对象,在源码中该方法返回的是null,所以最终结果返回null,
V createdValue = create(key);
if (createdValue == null) {
return null;
}
// 如果我们覆写掉create方法,使其返回不为null,那么就继续走下面这段代码,将创建的对象保存在linkedHashMap中,在这里就涉及到多线程问题,如果多线程操作时,在走到下面同步代码块之前,调用了put方法往linkedHashMap中存入了key对应的元素,那么在这里调用put方法后,返回的mapValue就不为空,然后走if中的代码,将put方法存入的对象重新存一边,弃用create()方法获取的对象,也就是说在这里优先使用put方法存入的对象;然后再紧接着走下面同步代码块后面的if...else中if里面的代码,调用entryRemoved()方法,然后返回;如果mapValue仍然返回null,那么就调用safeSizeOf()方法计算添加后map中对象占总内存的大小,然后走同步代码块之后if..else中else中代码,调用trimToSize()方法之后,返回
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue); // 计算内存中对象的总大小
}
}
if (mapValue != null) { // 1
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize); // 判断是否超过设定的最大内存,如果超过,就会移除掉最先存入的对象
return createdValue;
}
}
//create方法
protected V create(K key) {
return null;
}
//safeSizeOf方法
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
// entryRemoved方法
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
put方法
//调用put方法的时候,计算添加后内存中对象占的空间总大小,在添加过程中如果之前存入过,释放之前存入的对象,同时重新获取释放之后linkedHashMap中对象占内存的大小,然后再每次添加需要判断是否占有的总内存大小是否超过设定的内存大小,如果超过,会移除掉最早添加的元素。
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(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;
}
remove方法
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V previous;
synchronized (this) {
previous = map.remove(key); //移除linkedHashMap中保存的元素
if (previous != null) {
size -= safeSizeOf(key, previous); // 计算移除之后linkedHashMap中元素占内存的总大小
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
trimToSize方法(重点)
//LruCache之所以能够在保存的对象占用的总空间超过设定的大小时,及时对最早最不常用的对象进行释放,最主要的就是下面这段代码。
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!");
}
// 只有在linkedHashMap中对象总大小小于等于设定的最大值,或者linkedHashMap为空的时候,才会跳出循环,否则会每次都移除链表头部的元素,并进入下一次循环
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);
}
}