首页 Android内存缓存
文章
取消

Android内存缓存

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. 提供访问方法 ``` 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;
} ```
  1. 取缓存 ``` /**
    • 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; } } ```

  2. 存缓存 ``` /**
    • 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; } ```

  3. 清掉占用的内存空间 ``` /**
    • 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 } ```
本文由作者按照 CC BY 4.0 进行授权

Andriod源码之IntentService用法与原理

Android磁盘缓存