背景:最近,公司的项目中,需要使用大量的图片,频繁的进行bitmap的创建与销毁,内存开销太大,所以需要进行图片的缓存,经过查找相关资料,决定使用LruCache。
介绍:谷歌官方提供的一种内存缓存策略,当缓存达到最大值时,使用最近最少使用算法,剔除旧的缓存。
简单使用:
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
//计算最大可用内存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//取1/8作为缓存
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
//返回,当前消耗掉的空间大小
//注意,此处的单位要和cacheSize的单位保持一致!
return bitmap.getByteCount() / 1024;
}
};
...
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
复制代码
源码分析:LruCache 内部使用LinkedHashMap进行数据存储,当某个value(缓存值)被命中后,该value会被移动到队列的头部。当缓存满了之后,队列尾部的value就会被移除,被移除的值,可能会被垃圾回收器进行回收(如果被缓存的value,需要主动的回收内存,则需要重写entryRemoved方法,进行主动回收);LruCache 是多线程安全的,内部自动进行了同步。
put方法分析:
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
//key和value不能为null!!!
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
//进行同步
synchronized (this) {
putCount++;
//size:当前已用的缓存空间,累加
//safeSizeOf中调用的就是,我们重写的sizeOf,获取的当前值的占用空间大小
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
//说明是重复缓存,新的value会替换掉旧的value,需要重新计算size
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
//false,标明,该value不是因为空间不足被清除的,如果该value占用的内存
//需要被显示释放,则需要重写下面这个方法。
entryRemoved(false, key, previous, value);
}
//重新计算size大小,保证新加入的value,不会超过maxSize,如果超过了,
//则会移除队列尾部的value
trimToSize(maxSize);
return previous;
}
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
复制代码
get方法分析:
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created.
*/
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++;
}
/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/
//缓存中,没有找到,则进行创建,该方法是个空实现,可根据需求,
//确定是否进行创建
V createdValue = create(key);
if (createdValue == null) {
return null;
}
//如果创建成功,则进行插入,再返回。
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
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;
}
}
复制代码
trimToSize方法分析:
/**
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
*/
private void trimToSize(int maxSize) {
//直到size <= maxSzie 或者 map没有缓存了,才退出循环
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!");
}
//如果,size,没有达到最大缓存值,直接结束循环
if (size <= maxSize) {
break;
}
//获取使用次数最少的value
Map.Entry<K, V> toEvict = null;
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
//移除该缓存
map.remove(key);
//重新计算size
size -= safeSizeOf(key, value);
evictionCount++;
}
//true,表明,该value是因为缓存不足,被移除的
entryRemoved(true, key, value, null);
}
}
复制代码
清空所用缓存,可以直接调用evictAll()方法。
多说一句,关于bitmap是否需要显示调用recycle(),进行内存回收,看了下文档,现在是不需要显示调用的,GC会进行回收,文档如下:
//This is an advanced call, and normally need not be called,
//since the normal GC process will free up this memory when
//there are no more references to this bitmap.
public void recycle() {
...........................
}
}
复制代码
才疏学浅,如有错误,还请指正,谢谢!