深入理解安卓LruCache

请尊重作者劳动成果,转载请标明原文链接:http://blog.csdn.net/CapZhaot/article/details/51721828

1. Android中缓存策略:

缓存策略在安卓中有着广泛的应用,因为在移动端,流量对于用户来说是一种宝贵的资源,作为开发人员,应尽可能少的去减少网络流量的消耗.那么,如何避免过多的流量消耗呢? 答案便是使用缓存技术.当程序第一次从网络上下载到图片后,就把它缓存在存储设备上(内存或者硬盘),再次加载时就不需要再从网络上下载.
图片的缓存一般放在硬盘上,但是为了更好的用好体验,往往还会把图片在内存中再缓存一份,这样当程序 加载图片时,会首先从内存中获取,如果没有,再从硬盘中获取,如果硬盘中还没有,再从网络上下载一份,这种做法一般称之为三级缓存技术,即内存,硬盘,网络.本篇文章主要讲解在内存缓存方面的技术和以及自己学习时比较困惑的问题.主要是对LruCache(近期最少使用算法)的讲解.


2. 如何避免缓存时的内存溢出?

既然图片要在内存中缓存一份,那么最主要的问题就来了,安卓系统为每一个app分配的内存大小都是有限的,现在的图片的分辨率又这么高,如何保证在缓存时不发生OOM(内存溢出)?

2.1 使用软引用,弱引用:

首先明白强引用,弱引用,软引用的区别:

强引用:
直接的对象引用.
软引用(SoftReference):
如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它;如果内存空 间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
弱引用(WeakReference):
如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

虽然使用软引用,弱引用能够有效减少内存的OOM,但是这种方式现在已经不再推荐使用了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

2.2 LruCache缓存策略:

LruCache是Android3.1所提供的一个缓存类,它内部采用了一个LinkedHashMap以强引用的方式来存储外界对象,提供了基本的get和put方法进行缓存对象的获取和添加操作,并且此时会把该对象放到队首,表示该对象现在是最经常使用的,排在队尾的元素就非常危险了,因为当该map队列满的的时候,此时再放入数据,排在队尾的元素就会被删除了,以维持LruCache缓存对象的大小不超过用户设定的最大值.


3. LruCache类源码解析:


/*


package android.util;

import java.util.LinkedHashMap;
import java.util.Map;


public class LruCache<K, V> {
    //可以看出,LruCache内部维护着一个LinkedHashMap对象,所有的缓存对象都被放在了里面
    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;//丢失的次数

    /**
     * 新建LruCache时指定最大容量
     */
    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);
    }

    /**
     * 设置cache的最大容量
     *
     * @param maxSize 最大缓存容量.
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

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

    /**
     * 把缓存对象放入到容器中,并把它放在队首.
     * 
     */
    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;
    }

    /**
     * 检查容器的实际大小,以确保它小于指定的最大容量
     * 
     */
    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);
        }
    }

    /**
     * 
     *删除某个缓存对象
     * 
     */
    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;
    }

     /**
     * 
     *创建缓存对象,默认什么都不做,相当于是提供了一个接口,具体创建逻辑让用户自己实现.
     * 
     */
    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;
    }

     /**
     * 
     *计算每个缓存对象的 大小
     * 
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * 清空容器
     */
    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);
    }
}

3.1 重点方法介绍:

public LruCache(int maxSize):
通过构造函数来指定缓存容器的最大值,该最大值一般选取为可用内存的1/8,如下:

    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  //单位为KB

    int cacheSize = maxMemory / 8;  
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
        @Override  
        protected int sizeOf(String key, Bitmap bitmap) {  
            // 重写此方法来衡量每张图片的大小,默认返回图片数量。  
            return bitmap.getByteCount() / 1024;  //单位为KB
        }  
    };  

这里要注意一点,这里指定的最大值的单位要和sizeof方法指定的单位一致,比如上面都除以1024都是为了使其单位都为KB,当然也可以不除,但是要不除都不除,总之要一致.

protected int sizeOf(K key, V value):
因为每当LruCache进行添加(put方法)或者删除时,他需要实时计算这个容器还有多大,以保证当前缓存的大小不大于maxSize.因此,他就必须知道添加和删除时该item的大小,拿到这个数据再进行计算.
从源码可以看出:

    protected int sizeOf(K key, V value) {
        return 1;
    }

LruCache对sizeOf并没有实现,只是简单的返回了1,这显然不对,因此需要使用者重写该方法来正确的返回添加或删除item的实际大小.

public void trimToSize(int maxSize) :
检查当前容器的值是否超过了最大值,超过了把最旧的一个值删掉,没超过就不做任何处理..


3.2 难理解以及需要注意的地方:

3.2.1 添加或者获取item时是如何把item放到队首的?

在分析LRUCache前先对LinkedHashMap做些介绍。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,这种顺序有两种,一种是LRU顺序,一种是插入顺序,这可以由其构造函数public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定。所以,对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。
以get操作为例,如果是LRU顺序(accessOrder为true),Entry的recordAccess方法就调整get到的Entry到链表的头部去:

public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }

因此,整个LruCache的核心就是LinkedHashMap,它的添加和获取时把元素排到队尾的特性构成了近期最少使用算法的核心思想.

3.2.2 兼容性的处理:

开发时为了兼容到2.2的版本,在使用LruCache时建议采用support-v4兼容包提供的LruCache,而不要直接使用Android3.1中提供的LruCache.


如果有不正之处,希望谅解和批评指正,不胜感激。


参考文章:
Android 图片三级缓存之内存缓存(告别软引用(SoftRefrerence)和弱引用(WeakReference))
Java 如何有效地避免OOM:善于利用软引用和弱引用
解析Android开发优化之:软引用与弱引用的应用
LRUCache和FastLRUCache实现分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值