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. 初始化

    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;
    }
    };
  2. 提供访问方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
    if (getBitmapFromMemCache(key) == null) {
    mMemoryCache.put(key, bitmap);
    }
    }

    public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
    }

上面使用了系统分配给应用程序的八分之一作为缓存大小,在一些手机上大概是4M的内存空间,通常我们在加载图片的时候会先去缓存中取,如果取不到,则会开启一个后台线程去加载图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
}

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;
    }
  2. 取缓存

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

    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;
    }
    }
  3. 存缓存

    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
    /**
    * 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;
    }
  4. 清掉占用的内存空间

    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
    118
    119
    120
    /**
    * 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 || 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.
    *
    * <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.
    */
    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.
    */
    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
    }
分享到:
移动开发者/技术爱好者/喜欢开源与分享,你也可以关注微信公众号MobDevGroup,移动开发在线分享:MobDevGroup