您的位置:首页 > 其它

lruCache使用用于图片缓存

2015-03-04 15:42 197 查看
在开发android app时当应用加载大量图片时,我们必须要使用缓存技术来处理内存溢出问题,缓存技术主要可以分为内部缓存和外部缓存,这一节主要讲一下内部缓存一种实现。

LruCache缓存技术在android3.1(api12)添加的,位于android.util.lruCache,下面首先看一下文档介绍:

[java] view
plaincopy





A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue.

When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.

这里主要大体内容是:该缓存持有的是强引用,每当对象值被访问时,这个值对象会被移动到队列的头部。当添加缓存对象到缓存,缓存队列已满时,在缓存队列尾部的对象会被踢出,并被垃圾回收器回收。

从上面文档介绍来看LruCache是使用队列来管理缓存对象,使用频率较高的对象会被放到队列的头部,不长使用的对象位于队列尾部,当缓存达到最大值时,优先回收队列尾部不经常使用的缓存对象。

下面进入源码详细分析

[java] view
plaincopy





/**

* @param maxSize

* for caches that do not override {@link #sizeOf}, this is the

* maximum number of entries in the cache. For all other caches,

* this is the maximum sum of the sizes of the entries in this

* cache.

*/

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);

}

从构造方法我们可以看出缓存队列实现是LinkedHashMap实现传入true表示队列是有序的,

再看put()方法实现:

[java] view
plaincopy





/**

* 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) {

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;

}

从方法开始我们可以看出对于加入缓存的key,value均不能为空,否则会有异常抛出,所以我们put时需要进行必要判断处理。LruCache对每个放入缓存对象大小进行计算,加入总缓存大小。这里需要注意的是如果新加入的key值原来已经存在,即原缓存被覆盖时,需要将原对象缓存大小从总缓存中去掉,最后对整个缓存大小进行判断是否超过最大缓存数值并进行处理。

下面看一下缓存判断逻辑处理方法trimToSize

[java] view
plaincopy





/**

* @param maxSize

* the maximum size of the cache before returning. May be -1 to

* evict even 0-sized elements.

*/

private 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);

}

}

这里逻辑比较简单:这里采用循环判断,如果缓存queue当前大小超过最大值就删除队列中末尾不常用缓存对象,直到缓存达到在最大限制值之内。

在上述两个方法内我们会看的entryRemoved方法会被调用,源码如下:

[java] view
plaincopy





/**

* Called for entries that have been evicted or removed. This method is

* invoked when a value is evicted to make space, removed by a call to

* {@link #remove}, or replaced by a call to {@link #put}. The default

* implementation does nothing.

*

* <p>

* The method is called without synchronization: other threads may access

* the cache while this method is executing.

*

* @param evicted

* true if the entry is being removed to make space, false if the

* removal was caused by a {@link #put} or {@link #remove}.

* @param newValue

* the new value for {@code key}, if it exists. If non-null, this

* removal was caused by a {@link #put}. Otherwise it was caused

* by an eviction or a {@link #remove}.

*/

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {

}

居然是空,看来是为我们提供的接口。当缓存被覆盖或者被移除缓存队列时会调用entryRemoved()方法,第一个参数evicted true表示已达到缓存最大值被剔除,false表示缓存被新对象覆盖,我们可以重新该方法来监听哪个缓存对象值被回收了。值得注意的地方是当该方法被执行时,缓存对象被剔除并不是该对象立即被回收掉,可能还需要一定时间才能被垃圾回收器回收掉,如果内存资源十分紧张,我们就需要适当加速帮助垃圾回收器及时回收,这里已bitmap为例,需要调用oldValue
recycle 来加速垃圾回收。

最后看get方法实现:

[java] view
plaincopy





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);

}

}

get实现也很简单根据传入的key值进行取值,如果缓存对象存在直接返回,对象不存在则调用create方法。其中hitCount++为计算成功获取到值对象次数,missCount获取失败次数。顺便说一下create(),create()默认返回为null,我们可以重写此方法来处理获取缓存失败时处理逻辑,通常是调用put,网上很多例子都是另外自己写方法处理,其实完全没有那必要。直接重新即可,不用自己进行判断处理了,Lruchache已经提供好了接口,何必另造轮子?

通过以上简单分析已经对Lrucache原理大致了解差不多了,对于使用我们就更加轻松了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: