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);
}
}
}
内存缓存的目的是更快地为用户地呈现数据。对于加载数据这一操作,无论哪种缓存策略,其实质都是一样的,都是从相应数据结构中获取数据,然后进行加载。所以,不同的缓存策略的根本区别在于数据的存储方面,更具体点来说,就是当缓存达到上限,在替换对象时才真正体现了缓存策略的差异性。