LruCache缓存使用

1.为什么要缓存?

在android项目中经常需要请求网络图片,这个时候如果每张网络图片显示都要去获取一次的话,是非常耗费性能的。从网络中成功获取一张图片需要发起请求、等待响应、接收数据、解析数据、在做显示。这一系列操作我们都需要放到子线程中去执行,确认程序不会出现ANR问题。

对于缓存来说,无法就是添加缓存、删除缓存、获取缓存这三种操作。先判断是否有缓存,没有就进行缓存操作,当缓存满了之后我们就可以把最久没使用的缓存给清除掉,已达到不超出缓存边界的问题。

2.二种缓存方式

为了应对这种性能问题,我们有可以选择对图片进行缓存,在android中主要有二种方式来缓存,一种是内存缓存、磁盘缓存。内存缓存是最快的因为它是直接从内存中读取数据,磁盘缓存稍慢因为它要做IO流操作,但是它可以持久化数据,各有各的好处,所以我们可以结合情况二种方式都用上。

3.LruCache

结合以上出现的问题android在3.1的时候推出了一个LRU(Least Recently Used)最近期最少使用算法,它的核心思想就是,优先清除那些最近最少使用的对象。LRU体现有二种一个是内存中的LruCache和一个针对磁盘的DisLruCache。它们的核心都是LRU思想。

4.使用

这里初始化了一个LruCache类,构造函数我们传入了一个Int类型的 cacheSize参数,这个参数代表缓存的最大边界是当前显示的1/8的容量。

sizeOf

这个方法是计算每个缓存对象大小的,内部通过这个方法叠加大小看是否超过了,我们初始化构造函数中的大小。

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
private LruCache<String, Bitmap> mCache;
private Bitmap bitmap;
private LruCache<String, Bitmap> mCache;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_glide);
img = findViewById(R.id.iamge);
initLRUCache();
findViewById(R.id.but).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bitmap bitmap = getCacheImage("bitmap");
if (bitmap == null) {
requestImage();
} else {
Log.i("jinwei", "获取缓存");
img.setImageBitmap(bitmap);
}
}
});

private void initLRUCache() {
long maxMemory = Runtime.getRuntime().maxMemory();
int cacheSize = (int) (maxMemory / 8);
mCache = new LruCache<String, Bitmap>(cacheSize) {

@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
}



android.os.Handler handler = new android.os.Handler() {
@Override
public void dispatchMessage(Message msg) {
super.dispatchMessage(msg);
img.setImageBitmap(bitmap);
}
};

private void httpUrlConnectionImage() throws MalformedURLException {
Log.i("jinwei", "request Image");
URL url = new URL("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1535715922665&di=b706c8b7a4f907a07b1f5062073a4e2a&imgtype=0&src=http%3A%2F%2Fwww.baimg.com%2Fuploadfile%2F2015%2F0413%2FBTW_2015041347455.jpg");
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(5000);
connection.setConnectTimeout(5000);
InputStream inputStream = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
addCache("bitmap", bitmap);
handler.sendEmptyMessage(2);
} catch (IOException e) {
e.printStackTrace();
}
}

private void initLRUCache() {
long maxMemory = Runtime.getRuntime().maxMemory();
int cacheSize = (int) (maxMemory / 8);
mCache = new LruCache<String, Bitmap>(cacheSize) {

@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
}

/**
* 缓存取bitmap
*
* @param key key
* @return 缓存bm
*/
private Bitmap getCacheImage(String key) {
Bitmap mb = mCache.get(key);
return mb;
}

/**
* 移除缓存
*
* @param key
*/
private void removeCache(String key) {
mCache.remove(key);
}

/**
* 添加一个缓存
*
* @param key
* @param bitmap
*/
private void addCache(String key, Bitmap bitmap) {
if (getCacheImage(key) == null) {
mCache.put(key, bitmap);
}
}

private void requestImage() {
new Thread(new Runnable() {
@Override
public void run() {
try {
httpUrlConnectionImage();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}).start();
}

原理分析

可以看到构造里面做的操作,初始化可一个LinkedHashMap,所有LRU的核心就是通过hasmap来存储缓存对象的,maxSize保存了传入预设的大小值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   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;
private int createCount;
private int evictionCount;
private int hitCount;
private int missCount;

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

put方法

首先判断了key-value是否为null,为Null会抛出一个异常,所以LRU不支持key-value为null值。Lru的put方法是线程安全的,safeSizeOf就是会调用我们之前实现的sizeOf方法。

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
/**
* 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;
}

private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}

trimToSize

注释说明的检查最近最少使用的的对象进行清除,当然是要缓存超出边界了才会进行清除。size <= maxSize这个判断可以看出来。这个方法在put方法中调用,意思就是每次Put的时候都会检查当然的缓存容量,如果超出就会清除最近最少使用的对象。

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
 /**
* 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!");
}

if (size <= maxSize) {
break;
}

Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}

key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}

entryRemoved(true, key, value, null);
}
}

get

这个方法比较容易理解,mapValue = map.get(key);如果mapValue不为NUll就直接返回我们缓存的对象。

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
/**
* 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;
}
}

remove

remove直接会key 对应的对象清除掉,响应缓存容量也会减少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Removes the entry for {@code key} if it exists.
*
* @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;
}