内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
参考:
http://aww.qt06.com/news/index.php?site=Csdnblog&url=aHR0cDovL2Jsb2cuY3Nkbi5uZXQvanh4ZnpneS9hcnRpY2xlL2RldGFpbHMvNDQ4ODU2MjM=
http://blog.csdn.net/linghu_java/article/details/8574102
一般的实现如:
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) { @Override protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) { // evivted 只在lurcache trimtosize主动删除时返回true,其余情况如remove返回false // TODO if (Utils.sameBitmap(oldValue, newValue)) { return; } if (RecyclingBitmapDrawable.class.isInstance(oldValue)) { // The removed entry is a recycling drawable, so notify // it // that it has been removed from the memory cache ((RecyclingBitmapDrawable) oldValue).setIsCached(false); } else { Bitmap bitmap = oldValue.getBitmap(); if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); } } } @Override protected int sizeOf(String key, BitmapDrawable value) { final int bitmapSize = getBitmapSize(value) / 1024; return bitmapSize == 0 ? 1 : bitmapSize; } }; }
size 大小根据内存大小给个值单位于sizeof保持一致 如: Math.round(0.2f * Runtime.getRuntime().maxMemory() / 1024);
复写entryRemoved方法,注意evicted为true时为内存超限制删除元素 older的元素 要判断 不等于new元素,要recycle掉。
复写sizeof方法求bitmap大小 ,
@TargetApi(12) public static int getBitmapSize(BitmapDrawable value) { Bitmap bitmap = value.getBitmap(); if (Utils.hasHoneycombMR1() && bitmap != null) { try { Method method = bitmap.getClass().getMethod("getByteCount"); return (Integer) method.invoke(bitmap); } catch (NoSuchMethodException e) { LogUtil.e(TAG, e.getMessage()); } catch (IllegalArgumentException e) { LogUtil.e(TAG, e.getMessage()); } catch (IllegalAccessException e) { LogUtil.e(TAG, e.getMessage()); } catch (InvocationTargetException e) { LogUtil.e(TAG, e.getMessage()); } } // Pre HC-MR1 if (bitmap != null) { int byteCount = bitmap.getRowBytes() * bitmap.getHeight(); //修复因负数导致lruCache safeSizeof 的 IllegalStateException by ljh if(byteCount<0){ byteCount = 0; } } return 0; }
源代码分析:
LinkedHashMap的初始化
为什么用LinkedHashMap:可以使插入的顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历的时候会比HashMap慢。
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); }
put方法 如果已经含有元素 重新覆盖会改变顺序动态删除一些不常用的键值对,这个工作是由trimToSize方法完成的
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) {//之前已经插入过相同的key size -= safeSizeOf(key, previous);//那么减去该entry的容量,因为发生覆盖 } } if (previous != null) { entryRemoved(false, key, previous, value);//这个方法默认空实现 } trimToSize(maxSize);//若容量超过maxsize,将会删除最近很少访问的entry return previous; }
get方法
public void trimToSize(int maxSize) { while (true) {//不断删除linkedHashMap头部entry,也就是最近最少访问的条目,直到size小于最大容量 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 || map.isEmpty()) {//直到容量小于最大容量为止 break; } Map.Entry<K, V> toEvict = map.entrySet().iterator().next();//指向链表头 key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key);//删除最少访问的entry size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } }
get方法 注意:如果重写了create方法 value会再次加入缓存 重新排序
public final V get(K key) { if (key == null) {//不允许空键 throw new NullPointerException("key == null"); } V mapValue; synchronized (this) {//线程安全 mapValue = map.get(key);//调用LinkedHashMap的get方法 if (mapValue != null) { hitCount++;//命中次数加1 return mapValue;//返回value } missCount++;//未命中 } V createdValue = create(key);//默认返回为false if (createdValue == null) { return null; } synchronized (this) { createCount++;//如果创建成功,那么create次数加1 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; } }
remove 方法 注意 设置 entryRemoved 第一个参数为false
public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } V previous; synchronized (this) { previous = map.remove(key);//调用LinkedHashMap的remove方法 if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, null); } return previous;//返回value }
总结:
1.LruCache封装了LinkedHashMap,提供了LRU缓存的功能;
2.LruCache通过trimToSize方法自动删除最近最少访问的键值对;
3.LruCache不允许空键值;
4.LruCache线程安全;