【安卓中的缓存策略系列】安卓缓存之内存缓存LruCache

来源:互联网 发布:网络安全保密协议 编辑:程序博客网 时间:2024/06/13 10:22

缓存策略在移动端设备上是非常重要的,尤其是在图片加载这个场景下,因为图片相对而言比较大会花费用户较多的流量,因此可用缓存方式来解决,即当程序第一次从网络上获取图片的时候,就将其缓存到存储设备上,这样在用户下次使用这张图片时就不用从网络上再次获取,这样就能为用户节省一定的流量。这个功能目前绝大部分主流APP都会使用,如腾讯QQ,微信。但很多时候为了提高APP的用户体验,我们还需要把图片在内存中缓存一份,比如ListView,我们知道LIstView会在用户将某些图片移出屏幕后将其进行回收,此时垃圾回收器会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。可是为了能让程序快速运行,在界面上迅速地加载图片,必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况,而这种情况在ListView,GridView这种控件中出现是非常频繁的。采用内存缓存技术可以很好的解决上述问题,内存缓存技术允许控件可以快速地重新加载那些处理过的图片。内存缓存技术主要是通过LruCache这个类来完成的,下面从源码的角度详细讲解LruCache这个类,然后在此基础上讲解如何使用LruCache,让读者知其然更知其所以然。


一LruCache类:

首先我们来看一下类的定义及其构造函数:

public class LruCache<K, V> 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类使用泛型参数,其构造器参数为int型,该参数用来指定LruCache的大小,另外从其构造函数的实现过程来看,可以知道LruCache的底层是使用LinkedHashMap<K, V>来实现的,即LruCache使用一个强引用(strong referenced)的LinkedHashMap保存最近引用的对象。(A cache that holds strong references to a limited number of values.)

然后我么来看一下其重要的方法:

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 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;        }    } protected int sizeOf(K key, V value) {        return 1;    }

之所以把这三个方法拿出来讲解,是因为这三个方法它对应着我们实现的LruCache内存缓存策略的创建缓存,添加key到缓存,从缓存中获取V这三个最重要的功能,通常我们在创建缓存时会重写上述的sizeOf(K key, V value),在该方法中返回我们要创建的内存缓存的大小。而put与get则相对复杂些,我们首先来分析一下put方法。

可以看到put方法作用是缓存key,同时会将key移动到队列头部Caches {@code value} for {@code key}. The value is moved to the head of the queue.,另外它会返回与的key对应的先前的V,(return the previous value mapped by {@code key})。采用的是同步方式来实现的。

接下来我们看一下get方法。

从get方法中可以看到get包括两种情况:

1取的时候命中:直接返回与key对应的V。

2取的时候未命中:根据key产生一个V,然后调用put方法将其放入。

当get方法会将返回的值移动到队列头部。If a value was returned, it is moved to the  head of the queue。

从put与get方法可以看到,put与get时会将该元素放到队列的头部,因为无论是put还是get都表示该元素目前被使用过,所以会将其放到队列的头部,当缓存中的元素超过maxSize时,会通过trimToSize函数来去除缓存中最久的元素( Map.Entry<K, V> toEvict = map.eldest();),这就是所谓的LRU算法,即最近最少使用算法,即当当缓存中的元素超过maxSize时会淘汰最近最少使用的元素。下面是trimToSize的源码:

 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);        }    }

下面是LinkedHashMap中eldest()函数的代码:

 public Entry<K, V> eldest() {        LinkedEntry<K, V> eldest = header.nxt;        return eldest != header ? eldest : null;    }

通过上述代码的分析相信看官对LruCache的思想已经非常熟悉了,LruCache的底层是通过LinkedHashMap来实现的,当创建一个LruCache的对象时会让我们传入一个int型的maxSize,当我们向LruCache中put与get元素时会将该元素放到缓存队列的对头,当put元素超过maxSize时(这也是为何要传入maxSize参数的原因),会通过trimToSize函数来去除缓存中最久的元素,具体是通过Map.Entry<K, V> toEvict = map.eldest();来获取最久的元素,然后通过remove(key)的方式将其移除。


二LruCache的使用:

这个类是3.1版本中提供的,如果要在更早的Android版本中使用,则需要导入android-support-v4的jar包。
我们以BitMap对象来创建LruCache缓存为例,首先正如我在前面讲述的,一个LruCache缓存策略至少包含三个功能,即创建缓存,向缓存中添加元素,从缓存中获取元素。

代码如下:

private LruCache<String, Bitmap> mMemoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) {    ...    // Get max available VM memory, exceeding this amount will throw an    // OutOfMemory exception. Stored in kilobytes as LruCache takes an    // int in its constructor.    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);    // Use 1/8th of the available memory for this memory cache.    final int cacheSize = maxMemory / 8;    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {        @Override        protected int sizeOf(String key, Bitmap bitmap) {            // The cache size will be measured in kilobytes rather than            // number of items.            return bitmap.getByteCount() / 1024;        }    };    ...}public void addBitmapToMemoryCache(String key, Bitmap bitmap) {    if (getBitmapFromMemCache(key) == null) {        mMemoryCache.put(key, bitmap);    }}public Bitmap getBitmapFromMemCache(String key) {    return mMemoryCache.get(key);}
我们说过缓存的目的就是为了当加载某个图片时首先从LruCache 中检查是否存在这个Bitmap。如果确实存在,它会立即被用来显示到ImageView上,如果不存在,则会开启一个后台线程去处理显示该Bitmap任务。所以我们还需要为其添加一个loadBitmap的功能,代码如下:

public void loadBitmap(int resId, ImageView imageView) {    final String imageKey = String.valueOf(resId);    final Bitmap bitmap = getBitmapFromMemCache(imageKey);    if (bitmap != null) {        mImageView.setImageBitmap(bitmap);    } else {        mImageView.setImageResource(R.drawable.image_placeholder);        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);        task.execute(resId);    }}
其中的BitmapWorkerTask 需要把解析好的Bitmap添加到内存缓存中:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {    ...    // Decode image in background.    @Override    protected Bitmap doInBackground(Integer... params) {        final Bitmap bitmap = decodeSampledBitmapFromResource(                getResources(), params[0], 100, 100));        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);        return bitmap;    }    ...}


好了,以上就是本人理解的关于LruCache的知识,看官如果觉得不错,请点击下方的”顶“或“赞”按钮给我一点小小的鼓励哦!微笑,另外看官还可以看看我的其它博客的文章哦!微笑



5 0
原创粉丝点击