UIL(Android-Universal-Image-Loader)是Android平台上一个著名的图片加载库,虽然已经停止维护,但其强大的功能仍不可小觑,而其源码更是值得研究,其中有很多我们学习的地方。
接下来我们简单介绍一下UIL中的内存缓存机制。首先,UIL为所有的内存缓存方式提供了统一的接口,以实现缓存的增,删,改,查。其代码如下:
public interface MemoryCache { boolean put(String key, Bitmap value); Bitmap get(String key); Bitmap remove(String key); Collection<String> keys(); void clear(); }
对于不同的缓存策略,只需要各自实现这个接口即可。UIL提供了多种内存缓存策略,包括最近最少访问策略,最少使用策略,超时策略等。
最近最少使用策略:作为操作系统中的经典算法,自然也被广泛应用。其基本思想是:当缓存的内容达到缓存容量的上限时,再次缓存新的对象时,会先将最近最少使用的对象从缓存中移除,然后将新的对象添加到缓存中;
最少使用策略:所谓最少使用策略,就是保存了每个缓存对象的使用频率,当缓存的内容达到缓存容量的上限时,再次缓存新对象之前,会将最少被使用的对象从缓存中移除,然后将新的对象添加到缓存中;
超时策略:所谓的超时策略,就是为每一个缓存对象设置了超时时间。当缓存的内容达到缓存容量的上限时,再次缓存新对象之前,会先从缓存中移除已经超时的对象,然后再添加新的对象;
下面我们先贴一下UIL实现的内存缓存的基类,来了解一下内存缓存的基本实现过程:
public abstract class BaseMemoryCache implements MemoryCache { private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>()); @Override public Bitmap get(String key) { Bitmap result = null; Reference<Bitmap> reference = softMap.get(key); if (reference != null) { result = reference.get(); } return result; } @Override public boolean put(String key, Bitmap value) { softMap.put(key, createReference(value)); return true; } @Override public Bitmap remove(String key) { Reference<Bitmap> bmpRef = softMap.remove(key); return bmpRef == null ? null : bmpRef.get(); } @Override public Collection<String> keys() { synchronized (softMap) { return new HashSet<String>(softMap.keySet()); } } @Override public void clear() { softMap.clear(); } }
我们可以看到,所谓的内存缓存,通俗点来说就是一个Map结构的操作过程。但是具体使用哪种数据结构进行存储,还是需要根据缓存策略而定。
下面我们来看一下最近最少使用策略的数据缓存过程:
// 缓存数据 @Override public final boolean put(String key, Bitmap value) { if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } synchronized (this) { size += sizeOf(key, value); Bitmap previous = map.put(key, value); // 如果之前存在相同key的元素,则put是更新操作,需要减去之前对象的大小 if (previous != null) { size -= sizeOf(key, previous); } } // 释放超出限制的内存空间 trimToSize(maxSize); return true; } /* 释放超出限制的内存空间,LinkedHashMap本身就支持最近最少使用算法, 在遍历时,使用最少的对象默认都排在最前面 */ 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<String, Bitmap> toEvict = map.entrySet().iterator().next(); if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= sizeOf(key, value); } } }
内存缓存的目的是更快地为用户地呈现数据。对于加载数据这一操作,无论哪种缓存策略,其实质都是一样的,都是从相应数据结构中获取数据,然后进行加载。所以,不同的缓存策略的根本区别在于数据的存储方面,更具体点来说,就是当缓存达到上限,在替换对象时才真正体现了缓存策略的差异性。