1.概述
通常情况下,我们为了实现更好的用户体验从而引入了缓存的概念,这在Android应用于图片列表加载上显得更为重要。我们为了让内存维护在一个合理的范围,通常会把移除屏幕的图片进行回收处理,让GC去操作这些不在持有图片的引用,为了App有更流畅的体验,比如在界面上更加流畅的加载图片,而不得不考虑的一个问题就是图片回收之后,这时候用户又将刚刚回收的图片重新滑入屏幕内,这时候又回去加载一遍刚刚回收的图片,这无疑给性能带来了诸多问题,因此我们引入内存缓存。
2.原理
内存缓存对于那些大量占用程序宝贵内存资源的图片来说很好用,它提供了快速访问内存的方法,在过去,我们经常通过软引用或者弱引用(SoftReference or WeakReference)的方式,但是现在已经不推荐使用,自从Android 2.3(API Level 9)开始,JVM做了调整,使得垃圾回收器更容易倾向于回收持有软引用或者是弱引用的对象,折让弱引用或者软引用就变得不可靠,另外,Android3.0(API Level 11)中,图片的数据会存储在本地的内存中,也没有一个可预见的方式来释放它,这就给应用程序的崩溃或内存溢出埋下了隐患。因此在谷歌官方给我们在v4包中提供了LruCache类,它主要算法原理是将对象用强引用存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设值之前从内存中移除,来释放它。
1
2
3
4
5
6
7
8
9
10
11
12
/**
* @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的介绍
LinkedHashMap是HashMap的子类,它保留了插入的顺序,并维护者运行所有元素的一个双重链表,默认是按插入顺序排序(即构造函数最后一个参数false),如果指定按访问顺序排序(最近最少使用,构造函数最后一个为true),那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。
3.使用
- 初始化
1 2 3 4 5 6 7 8 9 10 11 12
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 // LruCache通过构造函数传入缓存值,以KB为单位。 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 使用最大可用内存值的1/8作为缓存的大小。 int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 重写此方法来衡量每张图片的大小,默认返回图片数量。 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); }
1
2
上面使用了系统分配给应用程序的八分之一作为缓存大小,在一些手机上大概是4M的内存空间,通常我们在加载图片的时候会先去缓存中取,如果取不到,则会开启一个后台线程去加载图片
public void loadBitmap(String url ,ImageView iv){ String key = md5(url); Bitmap bitmap = getBitmapFromMemCache(key); if(bitmap != null){ iv.setImageBitmap(bitmap); }else{ //TODO 开启线程去下载 } }
//在下载完图片后还需要保存在缓存中 class ImageDownLoaderTask extends AsyncTask<Integer,Void,Bitmap>{ @Override protected Bitmap doInBackground(Integer… params) { //取到Bitmap … … addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); return bitmap; } }
1
2
3
### 4.源码解析
1. 成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
private final LinkedHashMap<K, V> map;
/** Size of this cache in units. Not necessarily the number of elements. */
private int size; //已经存储的大小
private int maxSize; //规定的最大存储空间
private int putCount; //put的次数
private int createCount; //create的次数
private int evictionCount; //回收的次数
private int hitCount; //命中的次数
private int missCount; //丢失的次数
/**
* For caches that do not override {@link #sizeOf}, this returns the number
* of entries in the cache. For all other caches, this returns the sum of
* the sizes of the entries in this cache.
*/
public synchronized final int size() {
return size;
}
/**
* For caches that do not override {@link #sizeOf}, this returns the maximum
* number of entries in the cache. For all other caches, this returns the
* maximum sum of the sizes of the entries in this cache.
*/
public synchronized final int maxSize() {
return maxSize;
}
/**
* Returns the number of times {@link #get} returned a value that was
* already present in the cache.
*/
public synchronized final int hitCount() {
return hitCount;
}
/**
* Returns the number of times {@link #get} returned null or required a new
* value to be created.
*/
public synchronized final int missCount() {
return missCount;
}
/**
* Returns the number of times {@link #create(Object)} returned a value.
*/
public synchronized final int createCount() {
return createCount;
}
/**
* Returns the number of times {@link #put} was called.
*/
public synchronized final int putCount() {
return putCount;
}
/**
* Returns the number of values that have been evicted.
*/
public synchronized final int evictionCount() {
return evictionCount;
} ```
- 取缓存 ``` /**
- 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.
- 通过key来取对应的元素,或者返回新创建的元素,相应的这个元素会移动到队列的头部,如果这个元
素没有被缓存,也不能创建,则会返回null */ 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);
1 2 3 4 5 6
if (mapValue != null) { // There was a conflict so undo that last put map.put(key, mapValue);// 如果前面存在oldValue,那么就撤销最后一次的put } else { size += safeSizeOf(key, createdValue); } }
if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } } ```
- 存缓存 ``` /**
- 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);//返回的先前的value值 if (previous != null) { size -= safeSizeOf(key, previous); } }
if (previous != null) { entryRemoved(false, key, previous, value); }
trimToSize(maxSize); return previous; } ```
- 清掉占用的内存空间 ``` /**
- Remove the eldest entries until the total of remaining entries is at or
- below the requested size. *
- @param maxSize the maximum size of the cache before returning. May be -1
- to evict even 0-sized elements. */ 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!”); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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); } }
/**
- Removes the entry for {@code key} if it exists.
- 删除key相应的cache项,返回相应的value
@return the previous value mapped by {@code key}. */ public final V remove(K key) { if (key == null) { throw new NullPointerException(“key == null”); }
V previous; synchronized (this) { previous = map.remove(key); if (previous != null) { size -= safeSizeOf(key, previous); } }
if (previous != null) { entryRemoved(false, key, previous, null); }
return previous; }
/**
- 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. *
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) {}
/**
- Called after a cache miss to compute a value for the corresponding key.
- Returns the computed value or null if no value can be computed. The
- default implementation returns null. *
The method is called without synchronization: other threads may
- access the cache while this method is executing. *
If a value for {@code key} exists in the cache when this method
- returns, the created value will be released with {@link #entryRemoved}
- and discarded. This can occur when multiple threads request the same key
- at the same time (causing multiple values to be created), or when one
- thread calls {@link #put} while another is creating a value for the same
- key. */ protected V create(K key) { return null; }
private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException(“Negative size: “ + key + “=” + value); } return result; }
/**
- Returns the size of the entry for {@code key} and {@code value} in
- user-defined units. The default implementation returns 1 so that size
- is the number of entries and max size is the maximum number of entries.
- 默认返回元素的个数,一般我们来重写,比如上面我们让他返回占用的最小内存单元
An entry's size must not change while it is in the cache. */ protected int sizeOf(K key, V value) { return 1; }
/**
- Clear the cache, calling {@link #entryRemoved} on each removed entry.
- 清空cache */ public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements } ```