简书博客文章迁移https://www.jianshu.com/u/43a04ef9d4c6


浅析LruCache原理

Android用LruCache(Least recently use Cache 意思就是最近使用次数最少的那个对象)来取代原来强引用和软引用实现内存缓存,因为据说自2.3以后Android将更频繁的调用GC,导致软引用缓存的数据极易被释放。

LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用。如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。

他的主要原理在trimToSize方法中。

首先是LruCache声明的变量

    private final LinkedHashMap map;//LruCache关键的数据结构,用于存放数据   /**划重点,这俩个变量很关键*/    private int size;//当前LruCache的内存占用大小    private int maxSize;//LruCache的最大容量(通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少。)    private int putCount;//put的次数    private int createCount;//create的次数    private int evictionCount;//回收的次数    private int hitCount;//命中的次数    private int missCount;//丢失的次数

然后直接看关键方法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!");                }                //1 判断size是否超过maxSize                if (size <= maxSize || map.isEmpty()) {                    break;                }                //2 如果1处判断为不超过就取出最先插入的缓存                Map.Entry toEvict = map.entrySet().iterator().next();                key = toEvict.getKey();                value = toEvict.getValue();                //删除(这里是双向链表)                map.remove(key);                size -= safeSizeOf(key, value);//1                 evictionCount++;            }            entryRemoved(true, key, value, null);        }    }

这个方法是一个无限循环,跳出循环的条件是,size < maxSize或者 map 为空。主要的功能是判断当前容量时候已经超出最大的容量,如果超出了maxSize的话,就会循环移除map中的第一个元素,直到达到跳出循环的条件。

而双向链表LinkedHashMap(3个参数构造方法中accessOrder排序模式设置为访问模式时)中,每次get和put数据,则会将改对象移到链表的尾部,这样子内存缓存达到最大值时,map中的第一个元素就是最近最少使用的那个元素。此时,把它删除,从而达到避免OOM的出现。

注释1处safeSizeOf中封装了sizeOf方法,它是用来计算单个对象的大小,这里默认返回1,一般需要重写该方法来计算对象的大小,如果是计算bitmap的大小,这里会重写不返回1,而是返回bitmap的大小bitmap.getRowBytes() * bitmap.getHeight()


源码分析

变量

在上面了

构造函数

public LruCache(int maxSize) {    if (maxSize <= 0) {        throw new IllegalArgumentException("maxSize <= 0");    }    this.maxSize = maxSize;    this.map = new LinkedHashMap(0, 0.75f, true);//1}

这里设置了maxSize,以及实例化了一个LinkedHashMap对象,这个LinkedHashMap对象是实现Lru算法的关键,注释1处表示创建一个初始容量为0,加载因子是0.75(容量达到75%的时候把空间增大1半),最后这个参数上面也说了,是accessOrder,意思是排序模式,这里是true表示排序模式为访问模式(当map中数据有put和get时,当前操作的数据会移动到链表尾部)

put方法

    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++;//put的次数+1             size += safeSizeOf(key, value);  //size加上预put对象(value)的大小             previous = map.put(key, value);//previous为旧的值             if (previous != null) {                 //如果之前存在键为key的对象,则size应该减去原来对象的大小(把旧值大小删掉)                 size -= safeSizeOf(key, previous);             }         }         if (previous != null) {             entryRemoved(false, key, previous, value);//这个是空实现         }         //每次新加入对象都需要调用trimToSize方法看是否需要回收         trimToSize(maxSize);         return previous;     }

看注释应该明白了,先是增加size,然后判断以前有没有值,如果有就更新当前的额值,并且size要减去以前的值的大小

entryRemoved是一个空实现,如果我们使用LruCache的时候需要掌握元素移除的信息,可以重写这个方法来获取元素移除的信息。

get方法

    /**    通过key获取相应的item,或者创建返回相应的item。相应的item会移动到队列的尾部,    如果item的value没有被cache或者不能被创建,则返回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) {                  //mapValue不为空表示命中,hitCount+1并返回mapValue对象                  hitCount++; //命中 + 1                  return mapValue;              }              missCount++;  //未命中+1          }          /*           * 如果未命中,则试图创建一个对象,这里create方法返回null,并没有实现创建对象的方法           *如果需要事项创建对象的方法可以重写create方法。 假如在图片缓存中,因为图片缓存时内存缓存没有命中会去           * 文件缓存中去取或者从网络下载,所以并不需要创建。           */          V createdValue = create(key);//默认返回null(其实这里就是创建一个空对象的意思)          if (createdValue == null) {              return null;          }          //假如创建了新的对象,则继续往下执行          synchronized (this) {              createCount++;  //创建 + 1              //将createdValue加入到map中,并且将原来键为key的对象保存到mapValue              mapValue = map.put(key, createdValue);                 if (mapValue != null) {                  // There was a conflict so undo that last put                 //如果mapValue不为空,则撤销上一步的put操作。                 map.put(key, mapValue);             } else {                 //加入新创建的对象之后需要重新计算size大小                 size += safeSizeOf(key, createdValue);             }         }         if (mapValue != null) {             entryRemoved(false, key, createdValue, mapValue);             return mapValue;         } else {             //每次新加入对象都需要调用trimToSize方法看是否需要回收             trimToSize(maxSize);             return createdValue;         }     }

remove方法

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

从内存缓存中根据key值移除某个对象并返回该对象


实例

bb再多不如亲手操作一次

这里贴出ImageLoader类源代码,项目源代码PrivateTestProject

public class ImageLoader {    private LruCache mBitmapLruCache;    private RecyclerView recyclerView;//传过来的RecyclerView对象    private Set mTask = null;//存异步任务的set集合    public ImageLoader(RecyclerView recyclerView) {        this.recyclerView = recyclerView;        mTask = new HashSet<>();        int maxMemory = (int) Runtime.getRuntime().maxMemory();        int cacheSize = maxMemory / 8;        mBitmapLruCache = new LruCache(cacheSize) {            //重写            @Override            protected int sizeOf(String key, Bitmap value) {                //每次存入缓存即调用                return value.getByteCount();            }        };    }    /**     * 根据图片url下载图片     *     * @param imgUrl 图片路径     * @return bitmap     */    public Bitmap getBitmapByImgUrl(String imgUrl) {        Bitmap bitmap = null;        HttpURLConnection httpURLConnection = null;        try {            URL mUrl = new URL(imgUrl);            try {                httpURLConnection = (HttpURLConnection) mUrl.openConnection();                httpURLConnection.setConnectTimeout(10 * 1000);                httpURLConnection.setReadTimeout(10 * 1000);                bitmap = BitmapFactory.decodeStream(httpURLConnection.getInputStream());            } catch (IOException e) {                e.printStackTrace();            }        } catch (MalformedURLException e) {            e.printStackTrace();        } finally {            if (httpURLConnection != null) {                //断开连接                httpURLConnection.disconnect();            }        }        return bitmap;    }    /**     * 将bitmap加入cache     */    public void addBitmapToCache(String urlKey, Bitmap bitmap) {        mBitmapLruCache.put(urlKey, bitmap);    }    /**     * 从cache获取bitmap     */    public Bitmap getBitmapfromCache(String urlKey) {        return mBitmapLruCache.get(urlKey);    }    /**     * 取消所有下载异步任务     */    public void cancelAllTask() {        if (mTask != null) {            for (ImageLoaderTask task :                    mTask) {                task.cancel(false);            }        }    }    /**     * 按当前item的序号区间显示图片     */    public void showImages(int startIndex, int endIndex) {        for (int i = startIndex; i < endIndex; i++) {            String imageUrl = ImagCacheRAdapter.URLS[i];            Bitmap bitmap = getBitmapfromCache(imageUrl);//从缓存中获取            if (bitmap == null) {                //如果缓存为空,则开启异步线程                ImageLoaderTask imageLoaderTask = new ImageLoaderTask(imageUrl);                imageLoaderTask.execute();                //加入HashSet中                mTask.add(imageLoaderTask);            } else {                ImageView imageView = recyclerView.findViewWithTag(imageUrl);                imageView.setImageBitmap(bitmap);            }        }    }    /**     * 显示图片     *     * @param imageView     * @param imageUrl     */    public void showImage(ImageView imageView, String imageUrl) {        //从缓存中取图片        Bitmap bitmap = getBitmapfromCache(imageUrl);        //如果缓存中没有,则去下载        if (bitmap == null) {            imageView.setImageResource(R.mipmap.ic_launcher);        } else {            imageView.setImageBitmap(bitmap);        }    }    private class ImageLoaderTask extends AsyncTask<Void, Void, Bitmap> {        private String mImagerUrl;        public ImageLoaderTask(String mImagerUrl) {            this.mImagerUrl = mImagerUrl;        }        @Override        protected Bitmap doInBackground(Void... voids) {            //获取图片并且加入缓存            Bitmap bitmap = getBitmapByImgUrl(mImagerUrl);            if (bitmap != null) {                addBitmapToCache(mImagerUrl, bitmap);            }            return bitmap;        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            ImageView imageView = recyclerView.findViewWithTag(mImagerUrl);            if (null != imageView && null != bitmap) {                imageView.setImageBitmap(bitmap);            }            //显示成功后就把当前的AsyncTask从mTask中移除            mTask.remove(this);        }    }}public class ImageLoader {    private LruCache mBitmapLruCache;    private RecyclerView recyclerView;//传过来的RecyclerView对象    private Set mTask = null;//存异步任务的set集合    public ImageLoader(RecyclerView recyclerView) {        this.recyclerView = recyclerView;        mTask = new HashSet<>();        int maxMemory = (int) Runtime.getRuntime().maxMemory();        int cacheSize = maxMemory / 8;        mBitmapLruCache = new LruCache(cacheSize) {            //重写            @Override            protected int sizeOf(String key, Bitmap value) {                //每次存入缓存即调用                return value.getByteCount();            }        };    }    /**     * 根据图片url下载图片     *     * @param imgUrl 图片路径     * @return bitmap     */    public Bitmap getBitmapByImgUrl(String imgUrl) {        Bitmap bitmap = null;        HttpURLConnection httpURLConnection = null;        try {            URL mUrl = new URL(imgUrl);            try {                httpURLConnection = (HttpURLConnection) mUrl.openConnection();                httpURLConnection.setConnectTimeout(10 * 1000);                httpURLConnection.setReadTimeout(10 * 1000);                bitmap = BitmapFactory.decodeStream(httpURLConnection.getInputStream());            } catch (IOException e) {                e.printStackTrace();            }        } catch (MalformedURLException e) {            e.printStackTrace();        } finally {            if (httpURLConnection != null) {                //断开连接                httpURLConnection.disconnect();            }        }        return bitmap;    }    /**     * 将bitmap加入cache     */    public void addBitmapToCache(String urlKey, Bitmap bitmap) {        mBitmapLruCache.put(urlKey, bitmap);    }    /**     * 从cache获取bitmap     */    public Bitmap getBitmapfromCache(String urlKey) {        return mBitmapLruCache.get(urlKey);    }    /**     * 取消所有下载异步任务     */    public void cancelAllTask() {        if (mTask != null) {            for (ImageLoaderTask task :                    mTask) {                task.cancel(false);            }        }    }    /**     * 按当前item的序号区间显示图片     */    public void showImages(int startIndex, int endIndex) {        for (int i = startIndex; i < endIndex; i++) {            String imageUrl = ImagCacheRAdapter.URLS[i];            Bitmap bitmap = getBitmapfromCache(imageUrl);//从缓存中获取            if (bitmap == null) {                //如果缓存为空,则开启异步线程                ImageLoaderTask imageLoaderTask = new ImageLoaderTask(imageUrl);                imageLoaderTask.execute();                //加入HashSet中                mTask.add(imageLoaderTask);            } else {                ImageView imageView = recyclerView.findViewWithTag(imageUrl);                imageView.setImageBitmap(bitmap);            }        }    }    /**     * 显示图片     *     * @param imageView     * @param imageUrl     */    public void showImage(ImageView imageView, String imageUrl) {        //从缓存中取图片        Bitmap bitmap = getBitmapfromCache(imageUrl);        //如果缓存中没有,则去下载        if (bitmap == null) {            imageView.setImageResource(R.mipmap.ic_launcher);        } else {            imageView.setImageBitmap(bitmap);        }    }    private class ImageLoaderTask extends AsyncTask<Void, Void, Bitmap> {        private String mImagerUrl;        public ImageLoaderTask(String mImagerUrl) {            this.mImagerUrl = mImagerUrl;        }        @Override        protected Bitmap doInBackground(Void... voids) {            //获取图片并且加入缓存            Bitmap bitmap = getBitmapByImgUrl(mImagerUrl);            if (bitmap != null) {                addBitmapToCache(mImagerUrl, bitmap);            }            return bitmap;        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            ImageView imageView = recyclerView.findViewWithTag(mImagerUrl);            if (null != imageView && null != bitmap) {                imageView.setImageBitmap(bitmap);            }            //显示成功后就把当前的AsyncTask从mTask中移除            mTask.remove(this);        }    }}

ImageLoaderTask这里继承自AsyncTask,是ImageLoader的内部类

很显然,ImageLoader类最关键在LruCache对象mBitmapLruCache上,这里用了Set集合来使异步加载线程ImageLoaderTask是唯一的

ImageLoader主要逻辑是从内存LruCache对象中加载bitmap数据,如果没有,就网络加载数据变为Bitmap,然后存入LruCache对象mBitmapLruCache中。网络加载完的时候在异步线程中ImageLoaderTask把bitmap数据显示到ImageView

DiskLruCache实现硬盘缓存

Android照片墙完整版,完美结合LruCache和DiskLruCache

硬盘缓存通常用作三级缓存的第二层(本地持久化)

请参考 DiskLruCache基本用法

Android 缓存浅谈(二) DiskLruCache

大体套路

首先初始化,创建缓存目录以及线程池,然后加载图片时,先从缓存中获取(要在子线程中进行),如果缓存中有,则显示图片,如果没有则去下载并加入到缓存中,然后从缓存中获取,再显示。

如果在列表中可能会同时加载多个图片,如果只是一直创建线程,那么对app的性能以及体验都是考验,建议使用线程池机制**


参考

浅析LruCache原理

LruCache 实现原理分析

内存缓存LruCache实现原理

Android 缓存浅谈(一) LruCache

更多相关文章

  1. 【Android(安卓)Developers Training】 79. 连接到网络
  2. Android弹出DatePickerDialog并获取值的方法
  3. android Cursor的自动管理方式
  4. android动态获取权限方法
  5. Android之Handler与线程
  6. 【Android(安卓)动画】帧动画、补间动画、属性动画
  7. 【攻克Android(安卓)(43)】WebView (网络视图)
  8. Android(安卓)4.4 Kitkat Phone工作流程浅析(四)__RILJ工作流程
  9. Context 传递数据

随机推荐

  1. Android面试题目
  2. FrameLayout和RelativLayou上面覆盖View
  3. Android的常见控件(TextView、EditText、B
  4. Binder实现分析
  5. Android-传感器-实现记录人行走的轨迹
  6. Android性能优化典范(四)
  7. Android(安卓)结构组件之LiveData
  8. Service中弹出Dialog
  9. Android(安卓)setContentView 源码解析
  10. Android(安卓)开发艺术探索学习笔记(二)