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
这个方法是计算每个缓存对象大小的,内部通过这个方法叠加大小看是否超过了,我们初始化构造函数中的大小。
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保存了传入预设的大小值。
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方法。
/**
* 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的时候都会检查当然的缓存容量,如果超出就会清除最近最少使用的对象。
/**
* 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就直接返回我们缓存的对象。
/**
* 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 对应的对象清除掉,响应缓存容量也会减少。
/**
* 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;
}