(1)LruCache原理分析
简书博客文章迁移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
更多相关文章
- 【Android(安卓)Developers Training】 79. 连接到网络
- Android弹出DatePickerDialog并获取值的方法
- android Cursor的自动管理方式
- android动态获取权限方法
- Android之Handler与线程
- 【Android(安卓)动画】帧动画、补间动画、属性动画
- 【攻克Android(安卓)(43)】WebView (网络视图)
- Android(安卓)4.4 Kitkat Phone工作流程浅析(四)__RILJ工作流程
- Context 传递数据