63.ImageLoader源代码分析-内存缓存算法
一. 前言
图片内存缓存可以提高图片显示速度,但是有些问题,比如占用内存,如果不加以控制,甚至可能会OOM
所以,需要提供各种各样的算法来控制内存的使用,以适应不同的使用场景,目前,ImageLoader提供了若干内存管理算法。
默认内存缓存是关闭的,需要手动打开
二. 继承关系图
三. 主要内存算法介绍
算法 | 解释 |
---|---|
MemoryCache | Interface 内存缓存的接口 |
MemoryCache | Interface 内存缓存的接口 |
FuzzyKeyMemoryCache | 模糊Key内存缓存,一些不同的Key可能会被认为是相同(通过equals方法)。当你put一个值到cache里面时,equals相同的Key也会被替换。这是一个内部功能,通常我们不需要使用到它。 |
LimitedAgeMemoryCache | 限制时间内存缓存,如果一些对象的使用时间超过了定义的值,那么就会移除。比如最大时间设置成60s,那么如果一个对象是60s前put进来的,那么它就会失效。 |
LruMemoryCache | 一个强引用的图片,限制图片使用数量的内存缓存,每个被访问的图片都会移动到队列的头部。当一个图片添加到一个满的队列的时候,队尾的图片将会被驱逐而被垃圾回收器回收。 |
BaseMemoryCache | 抽象类,基本的图片缓存管理,提供了图片存储(强引用或者弱引用),具体的可以看下面的MemoryCache实现。 |
WeakMemoryCache | 继承自BaseMemoryCache,弱引用内存缓存,弱引用,当内存不足的时候,会被系统回收。 |
LimitedMemoryCache | 抽象类,继承自BaseMemoryCache,限制内存的内存管理抽象类,包括下面4个具体实现。提供图片对象存储,存储的所有的图片大小将不会超过大小限制。这个缓存提供了强引用和弱引用存储了图片,强引用用来限制图片大小,弱引用用来其他缓存的图片。 |
FIFOLimitedMemoryCache | 继承自LimitedMemoryCache,存储的图片大小不小超过限定的大小。当缓存图片到达指定大小时,会按照FIFO(先进先出)的原则清理图片。 |
LRULimitedMemoryCache | 继承自LimitedMemoryCache,存储的图片大小不小超过限定的大小。当缓存图片到达指定大小时,最早使用的图片会从缓存中删除。 |
LargestLimitedMemoryCache | 继承自LimitedMemoryCache,存储的图片大小不小超过限定的大小。当缓存图片到达指定大小时,最大的图片会从缓存中删除。 |
UsingFreqLimitedMemoryCache | 继承自LimitedMemoryCache,存储的图片大小不小超过限定的大小。当缓存图片到达指定大小时,最少使用频率的图片会从缓存中删除。 |
下面,我们会具体看各个MemoryCache的实现
四. LimitedAgeMemoryCache
public class LimitedAgeMemoryCache implements MemoryCache { private final MemoryCache cache; private final long maxAge; private final Map loadingDates = Collections.synchronizedMap(new HashMap()); @Override public boolean put(String key, Bitmap value) { boolean putSuccesfully = cache.put(key, value); if (putSuccesfully) { loadingDates.put(key, System.currentTimeMillis()); } return putSuccesfully; } @Override public Bitmap get(String key) { Long loadingDate = loadingDates.get(key); if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) { cache.remove(key); loadingDates.remove(key); } return cache.get(key); }}
实现的方法很简单,在put的时候,用一个Map记录了存入的时间,get的时候,判断时间是否大于maxAge,如果是,那么就删掉这个缓存。
五. LruMemoryCache
public class LruMemoryCache implements MemoryCache { private final LinkedHashMap map; private final int maxSize; @Override public final boolean put(String key, Bitmap value) { ... synchronized (this) { size += sizeOf(key, value); Bitmap previous = map.put(key, value); if (previous != null) { size -= sizeOf(key, previous); } } trimToSize(maxSize); return true; } private void trimToSize(int maxSize) { while (true) { String key; Bitmap 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 toEvict = map.entrySet().iterator().next(); if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= sizeOf(key, value); } } }}
实现的原理是利用LinkedHashMap,先存入的key在遍历的时候在链表的头部,所以在put值的时候,从头开始遍历HashMap,从头删除图片缓存,直到内存的大小在指定大小以内。
另外为了防止多线程的问题,put和trimToSize删除图片的时候做了synchronized同步。
这个也是ImageLoader使用比较多的一种内存管理方法。
六. WeakMemoryCache
public class WeakMemoryCache extends BaseMemoryCache { @Override protected Reference createReference(Bitmap value) { return new WeakReference(value); }}
实现方法很简单,用一个弱引用就可以解决,用弱引用包裹了图片。
七. FIFOLimitedMemoryCache
FIFOLimitedMemoryCache继承自抽象类LimitedMemoryCache,所以看算法实现前还要看LimitedMemoryCache代码。
抽象类LimitedMemoryCache和其实现子类的关系是LimitedMemoryCache put的时候限定超过大小时调用removeNext删除图片缓存;
但是具体怎么删,删除什么图片缓存,就由子类实现。
public abstract class LimitedMemoryCache extends BaseMemoryCache { @Override public boolean put(String key, Bitmap value) { boolean putSuccessfully = false; // Try to add value to hard cache int valueSize = getSize(value); int sizeLimit = getSizeLimit(); int curCacheSize = cacheSize.get(); if (valueSize < sizeLimit) { while (curCacheSize + valueSize > sizeLimit) { Bitmap removedValue = removeNext(); if (hardCache.remove(removedValue)) { curCacheSize = cacheSize.addAndGet(-getSize(removedValue)); } } hardCache.add(value); cacheSize.addAndGet(valueSize); putSuccessfully = true; } // Add value to soft cache super.put(key, value); return putSuccessfully; }} public class FIFOLimitedMemoryCache extends LimitedMemoryCache { private final List queue = Collections.synchronizedList(new LinkedList()); ... @Override protected Bitmap removeNext() { return queue.remove(0); }}
用list实现,直接从头开始移除。
八. LRULimitedMemoryCache
public class LRULimitedMemoryCache extends LimitedMemoryCache { /** Cache providing Least-Recently-Used logic */ private final Map lruCache = Collections.synchronizedMap(new LinkedHashMap(INITIAL_CAPACITY, LOAD_FACTOR, true)); @Override protected Bitmap removeNext() { Bitmap mostLongUsedValue = null; synchronized (lruCache) { Iterator> it = lruCache.entrySet().iterator(); if (it.hasNext()) { Entry entry = it.next(); mostLongUsedValue = entry.getValue(); it.remove(); } } return mostLongUsedValue; }}
使用一个 LinkedHashMap lruCache来保存图片的put顺序,需要移除的时候(removeNext)从lruCache找到第一张图片即可。
九. UsingFreqLimitedMemoryCache
public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache { private final Map usingCounts = Collections.synchronizedMap(new HashMap()); @Override public Bitmap get(String key) { Bitmap value = super.get(key); // Increment usage count for value if value is contained in hardCahe if (value != null) { Integer usageCount = usingCounts.get(value); if (usageCount != null) { usingCounts.put(value, usageCount + 1); } } return value; } @Override protected Bitmap removeNext() { Integer minUsageCount = null; Bitmap leastUsedValue = null; Set> entries = usingCounts.entrySet(); synchronized (usingCounts) { for (Entry entry : entries) { if (leastUsedValue == null) { leastUsedValue = entry.getKey(); minUsageCount = entry.getValue(); } else { Integer lastValueUsage = entry.getValue(); if (lastValueUsage < minUsageCount) { minUsageCount = lastValueUsage; leastUsedValue = entry.getKey(); } } } } usingCounts.remove(leastUsedValue); return leastUsedValue; }}
使用一个HashMap usingCounts来存储bitmap的使用次数,每次get的时候都会让使用次数+1,那需要移除的时候图片缓存的时候,遍历usingCounts,找到使用次数最少的图片,删除之。
不过这种遍历的算法感觉效率不高,慎用!