Android开发难免会遇到加载网络图片问题,而加载网络图片难免会遇到多次重复请求加载同一张图片问题,这么一来就导致多次加载网络,不仅浪费资源而且给用户体验感觉加载图片慢。从Android3.1开始,API中多了一个LruCache类,该类是一个使用最近最少使用算法的缓存类。也就是可以把上次下载的图片以键值对的方式保存在缓存中,以便下载加载同一张图片时无须再次从网络上下载,而且加载速度快。
这里我把LruCache源码贴出来,几乎每行代码的注释都有,很详细,有意者可以仔细跟着注释阅读源码,以便理解LruCache类的原理。
/** 1.从类名LruCache就知道,该类的作用是一个最近最少使用算法来维护的一个缓存类。 2.该类是一个用于缓存一定数量的值,并且该缓存对象是持有强引用。 3.每当成功get一次值时,该值都会移动到链表的头部,以便标记该值为最近最新的值。 4.当缓存满了时,链表尾部的值会被认为是最近最少使用的值,会被从链表中移除,以便缓存有空间保存新的值。 5.缓存中链表的值发生改变时会调用空方法entryRemoved,开发者可以重写该方法,以便做相应的操作。 6.开发者应该去重写该类中sizeOf方法,该方法返回每个key对应value值得大小,以便该类去维护缓存的大小。 7.该类是一个安全类。同时该类不允许key和value为空。该类在Android3.1之后添加到源码中。 **/
package android.util;
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache<K, V> {
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;//未命中次数
/** * @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);//创建链表缓存,该链表缓存是整个LruCache类的重点。
}
/** * Sets the size of the cache. * @param maxSize The new maximum size. *重新设置缓存大小 * @hide */
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
/** * 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值获得缓存中对应的数据,该方法是线程安全的。 */
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);//创建新的数据,默认返回null,创建的时候可以重写该方法,以便未命中的时候创建一个新的数据添加到缓存链表中。
if (createdValue == null) {
return null;//未命中时默认到这里结束。
}
//以下代码是在未命中时创建新数据添加到链表缓存中。
synchronized (this) {
createCount++;//标记创建新数据的次数
mapValue = map.put(key, createdValue);
//如果链表中已有该key对应的值,则最后取消添加新创建的值。很多人可能感到奇怪,此时链表中应该不存在key对应的值啊?其实这里是为了防止多线程操作导致数据不同步而添加的安全代码。不懂得自己体会去吧!
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
//标记已经使用了的内存大小。
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
//链表中发生数据变化时(调用了put,get方法)调用该方法。该方法默认是个空,开发者可以重写该方法在链表中数据变化时做相应的操作。
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);//保存数据到链表
if (previous != null) {//链表中已存在key对应的值则替换原来的值
size -= safeSizeOf(key, previous);//标记已使用的内存大小
}
}
if (previous != null) {
//链表中数据发生交换时调用该方法。
entryRemoved(false, key, previous, value);
}
//整理链表维护的内存大小
trimToSize(maxSize);
return previous;
}
/** * @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) {
break;
}
// BEGIN LAYOUTLIB CHANGE
// get the last item in the linked list.
// This is not efficient, the goal here is to minimize the changes
// compared to the platform version.
Map.Entry<K, V> toEvict = null;
//for循环得到链表末尾的数据,然后移除它。
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
// END LAYOUTLIB CHANGE
if (toEvict == null) {
break;
}
//移除链表末尾的一个数据,该数据就是最近最少使用到的数据。
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. * * @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. * * <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) {}
/** * 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. * * <p>The method is called without synchronization: other threads may * access the cache while this method is executing. * * <p>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. */
//该方法默认返回空,开发者可以重新该方法以便在get链表中的数据失败时创建新的数据添加到链表中。
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. * * <p>An entry's size must not change while it is in the cache. */
//该方法默认返回1,开发者必须重写该方法,用来计算每个key对应value值得大小,以便维护链表中缓存的大小。
protected int sizeOf(K key, V value) {
return 1;
}
/** * Clear the cache, calling {@link #entryRemoved} on each removed entry. */
//移除链表中所有的数据
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/** * 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 a copy of the current contents of the cache, ordered from least * recently accessed to most recently accessed. * 得到链表对象 */
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}
@Override public synchronized final String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
maxSize, hitCount, missCount, hitPercent);
}
}
一下代码是图片缓存类ImageCache示例代码:
import android.graphics.Bitmap;
import android.util.LruCache;
/** * Created by xjp on 2016/4/2. */
public class ImageCache {
private LruCache mLruCache;
private static ImageCache instance = new ImageCache();
public ImageCache() {
//得到当前应用总的内存大小
int appTotalCache = (int) Runtime.getRuntime().totalMemory();
//取当前应用内存的1/8作为缓存大小
int maxSize = appTotalCache / 8;
mLruCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
//此处返回每个Bitmap对象的大小。值得注意:此处返回值并不是图片的张数,
// 且返回值的单位应该和maxSize的单位一样。也就是maxSize单位是B,那么此处返回值单位也是B
return value.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
//如果缓存中的值被移除,则去回收Bitmap内存。
if (evicted) {
if (oldValue != null && !oldValue.isRecycled()) {
oldValue.recycle();
}
}
}
};
}
public static ImageCache getInstance() {
return instance;
}
//从缓存中读取数据
public Bitmap getBitmapFromCache(String key) {
if (mLruCache != null) {
mLruCache.get(key);
}
return null;
}
//保存数据到缓存中
public void putBitmapToCache(String key, Bitmap value) {
if (mLruCache != null) {
if (mLruCache.get(key) == null) {
mLruCache.put(key, value);
}
}
}
//清除缓存中所有数据
public void clearCache() {
if (mLruCache != null) {
mLruCache.evictAll();
}
}
}
总结:以后就可以直接用ImageCache类来作为图片缓存了。