UIL源码解析之内存缓存

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);
        }
    }
}

总结

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

,