前言
记得很久之前我写了一篇banner的文章,好多朋友找我要代码,并要我开放banner中使用的图片管理工厂-ImageManager。如果想很好地理解下面的故事,请参看我半年前写的两篇博文:android中图片的三级cache策略(内存、文件、网络) 一和android中左右滑屏的实现(广告位banner组件)。
当时没有发上来是由于如下几点原因:首先代码较多,其次当时写的时候也参考了网络上存在的三级cache策略(大同小异),并且采用了Android项目中开源的LruCache页面淘汰算法(近期最少使用算法),还有一点就是这是实际项目使用的代码,不便直接开放,但是现在我决定把它稍作修改后开放给大家。这里我想说说那个banner,平心而论,banner的代码很多,如果采用ViewPager之类的则可以减少不少代码,但是我更看重banner的实现思想以及它的封装和事件传递,在自定义控件的封装和架构上,我到现在还觉得banner是及其成功的,尤其是banner和ImageManager结合以后,整个功能浑然天成,超高内聚,使用起来及其方便,最少只需要两行代码,你不需要导入xml,也不需要处理Json拉取策略,因为相关业务层都被封装在了banner内部,对外只保留很少的几个接口,只要实现它就能和banner内部进行交互。下面我将要介绍三级cache策略之二:内存缓存策略。
内存缓存策略
当有一个图片要去从网络下载的时候,我们并不会直接去从网络下载,因为在这个时代,用户的流量是宝贵的,耗流量的应用是不会得到用户的青睐的。那我们该怎么办呢?这样,我们会先从内存缓存中去查找是否有该图片,如果没有就去文件缓存中查找是否有该图片,如果还没有,我们就从网络下载图片。本博文的侧重点是如何做内存缓存。这里,我有必要说明下几个概念:强引用、软引用、弱引用、Lru。
强引用:就是直接引用一个对象,一般的对象引用均是强引用
软引用:引用一个对象,当内存不足并且除了我们的引用之外没有其他地方引用此对象的情况 下,该对象会被gc回收
弱引用:引用一个对象,当除了我们的引用之外没有其他地方引用此对象的情况下,只要gc被调用,它就会被回收(请注意它和软引用的区别)
Lru:Least Recently Used 近期最少使用算法,是一种页面置换算法,其思想是在缓存的页面数目固定的情况下,那些最近使用次数最少的页面将被移出,对于我们的内存缓存来说,强引用缓存大小固定为4M,如果当缓存的图片大于4M的时候,有些图片就会被从强引用缓存中删除,哪些图片会被删除呢,就是那些近期使用次数最少的图片。
代码
public class ImageMemoryCache { /** * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。 * 强引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。 */ private static final String TAG = "ImageMemoryCache"; private static LruCache<String, Bitmap> mLruCache; // 强引用缓存 private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; // 软引用缓存 private static final int LRU_CACHE_SIZE = 4 * 1024 * 1024; // 强引用缓存容量:4MB private static final int SOFT_CACHE_NUM = 20; // 软引用缓存个数 // 在这里分别初始化强引用缓存和弱引用缓存 public ImageMemoryCache() { mLruCache = new LruCache<String, Bitmap>(LRU_CACHE_SIZE) { @Override // sizeOf返回为单个hashmap value的大小 protected int sizeOf(String key, Bitmap value) { if (value != null) return value.getRowBytes() * value.getHeight(); else return 0; } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { if (oldValue != null) { // 强引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存 Logger.d(TAG, "LruCache is full,move to SoftRefernceCache"); mSoftCache.put(key, new SoftReference<Bitmap>(oldValue)); } } }; mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>( SOFT_CACHE_NUM, 0.75f, true) { private static final long serialVersionUID = 1L; /** * 当软引用数量大于20的时候,最旧的软引用将会被从链式哈希表中移出 */ @Override protected boolean removeEldestEntry( Entry<String, SoftReference<Bitmap>> eldest) { if (size() > SOFT_CACHE_NUM) { Logger.d(TAG, "should remove the eldest from SoftReference"); return true; } return false; } }; } /** * 从缓存中获取图片 */ public Bitmap getBitmapFromMemory(String url) { Bitmap bitmap; // 先从强引用缓存中获取 synchronized (mLruCache) { bitmap = mLruCache.get(url); if (bitmap != null) { // 如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除 mLruCache.remove(url); mLruCache.put(url, bitmap); Logger.d(TAG, "get bmp from LruCache,url=" + url); return bitmap; } } // 如果强引用缓存中找不到,到软引用缓存中找,找到后就把它从软引用中移到强引用缓存中 synchronized (mSoftCache) { SoftReference<Bitmap> bitmapReference = mSoftCache.get(url); if (bitmapReference != null) { bitmap = bitmapReference.get(); if (bitmap != null) { // 将图片移回LruCache mLruCache.put(url, bitmap); mSoftCache.remove(url); Logger.d(TAG, "get bmp from SoftReferenceCache, url=" + url); return bitmap; } else { mSoftCache.remove(url); } } } return null; } /** * 添加图片到缓存 */ public void addBitmapToMemory(String url, Bitmap bitmap) { if (bitmap != null) { synchronized (mLruCache) { mLruCache.put(url, bitmap); } } } public void clearCache() { mSoftCache.clear(); } }