Glide缓存机制

Glide缓存机制

Glide缓存非常先进,很灵活,很全面,总体上来讲有内存缓存和磁盘文件缓存。缓冲机制概括来讲就是读缓存以及是写入缓存的机制。而Glide读缓存时机就是先内存缓存查找再到磁盘缓存查找最后网络,写入缓存则就是在获取到原始source图片之后,先写入磁盘缓存,再加入内存缓存。

每个缓存查找都是通过key来查询,一般都是直接用下载url来作为key,那Glide的key呢。直接看下Engine .load源码作为文章开头概述:

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {

......

 public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();

        //生成key:参数包括width、height、等因素
        final String id = fetcher.getId();
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());

/*****************读取内存缓存 start*********************************/
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        if (cached != null) {
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            return null;
        }

        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) {
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }
/*****************读取内存缓存 end*********************************/

        EngineJob current = jobs.get(key);
        if (current != null) {
            current.addCallback(cb);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Added to existing load", startTime, key);
            }
            return new LoadStatus(cb, current);
        }

        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);

       /**********runnable 中会先读取disk磁盘缓存*********/
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
    }

....

}

可以看到,fetcher.getId()方法获得了一个id字符串,这个字符串也就是我们要加载的图片的唯一标识,比如说如果是一张网络上的图片的话,那么这个id就是这张图片的url地址。接下来,将这个id连同着signature、width、height等等10个参数一起传入到EngineKeyFactory的buildKey()方法当中,从而构建出了一个EngineKey对象,这个EngineKey也就是Glide中的缓存Key了,所以会根据这些参数生成不同的key,就意味着会有同一个图片会有多种缓存策略下,例如磁盘缓存策略就有

DiskCacheStrategy.NONE: 表示不缓存任何内容。
DiskCacheStrategy.SOURCE: 表示只缓存原始图片。
DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)。
DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片

一般默认Glide缓存下载图片之后根据ImageView大小而生成缓存图片,而不是原始图片。故会存在一张图片会对应有多个缓存图片。

内存缓存

1、LRU算法缓存
MemoryCache cache=new LruResourceCache(calculator.getMemoryCacheSize());
2、正在使用图片弱引用缓存
Map

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
    ...    

    @Override
    public void onEngineJobComplete(Key key, EngineResource<?> resource) {
        Util.assertMainThread();
        // A null resource indicates that the load failed, usually due to an exception.
        if (resource != null) {
            resource.setResourceListener(key, this);
            if (resource.isCacheable()) {
                activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
            }
        }
        jobs.remove(key);
    }

    ...
}

那什么时候写入LRU缓存中
EngineResource中的一个引用机制(图片引用), 调用acquire()方法会让变量加1,调用release()方法会让变量减1。当为0时,就会从加入到cache中

class EngineResource<Z> implements Resource<Z> {

    private int acquired;
    ...
 void release() {
        if (acquired <= 0) {
            throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
            throw new IllegalThreadStateException("Must call release on the main thread");
        }
        if (--acquired == 0) {
            listener.onResourceReleased(key, this);
        }
    }
public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
        .....
public void onResourceReleased(Key cacheKey, EngineResource resource) {
        Util.assertMainThread();
        activeResources.remove(cacheKey);
        if (resource.isCacheable()) {
            cache.put(cacheKey, resource);
        } else {
            resourceRecycler.recycle(resource);
        }
    }

结论:可以在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能

硬盘缓存

策略中有缓存转换后图片和原始图片之分,默认是转换后的。缓存算法依然是LRU算法为DiskLruCache实例。 在上文中可以看到EngineRunnable中会先从磁盘中解析缓存,其run函数中会调用decode函数来decodeFromCache。

 private Resource<?> decode() throws Exception {
        if (isDecodingFromCache()) {
            return decodeFromCache();
        } else {
            return decodeFromSource();
        }
    }

如果有缓存的话,会读取文件缓存,先看下读取缓存。

1、读取缓存

private Resource<?> decodeFromCache() throws Exception {
        Resource<?> result = null;
        try {
            result = decodeJob.decodeResultFromCache();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Exception decoding result from cache: " + e);
            }
        }

        if (result == null) {
            result = decodeJob.decodeSourceFromCache();
        }
        return result;
    }

可以看到,这里会先去调用DecodeJob的decodeResultFromCache()方法来获取缓存,如果获取不到,会再调用decodeSourceFromCache()方法获取缓存,这两个方法就是对应DiskCacheStrategy.RESULT和DiskCacheStrategy.SOURCE两种策略,两种缓存会都先查找,是否能找到就看对应策略是否有配置,通过结果来判断而不是根据事先的配置来判断,这样能共用代码和简化逻辑。

这两个都会通过loadFromCache来获取,只是传入的参数不一样,一个传入的是resultKey,另外一个却又调用了resultKey的getOriginalKey()方法,即对应缓存测试类型。源码如下

private Resource<T> loadFromCache(Key key) throws IOException {
    File cacheFile = diskCacheProvider.getDiskCache().get(key);
    if (cacheFile == null) {
        return null;
    }
    Resource<T> result = null;
    try {
        result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
    } finally {
        if (result == null) {
            diskCacheProvider.getDiskCache().delete(key);
        }
    }
    return result;
}

读取缓存就到这里,接下来看下如何写入磁盘缓存

写入缓存

从上文中看到先读取缓存,没有才decodeFromSource获取源头原始数据,那么在获取网络数据之后会不会缓存呢,这是最佳时候。

public Resource<Z> decodeFromSource() throws Exception {
    Resource<T> decoded = decodeSource();
    return transformEncodeAndTranscode(decoded);
}

这个方法中decodeSource()顾名思义是用来解析原图片的,而transformEncodeAndTranscode()则是用来对图片进行转换和转码的。我们先来看decodeSource()方法做了什么:

private Resource<T> decodeSource() throws Exception {
    Resource<T> decoded = null;
    try {
        long startTime = LogTime.getLogTime();
        final A data = fetcher.loadData(priority);
        if (isCancelled) {
            return null;
        }
        decoded = decodeFromSourceData(data);
    } finally {
        fetcher.cleanup();
    }
    return decoded;
}

private Resource<T> decodeFromSourceData(A data) throws IOException {
    final Resource<T> decoded;
    if (diskCacheStrategy.cacheSource()) {
        decoded = cacheAndDecodeSourceData(data);
    } else {
        long startTime = LogTime.getLogTime();
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
    }
    return decoded;
}

private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
    long startTime = LogTime.getLogTime();
    SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
    diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
    startTime = LogTime.getLogTime();
    Resource<T> result = loadFromCache(resultKey.getOriginalKey());
    return result;
}

可以看到先调用fetcher的loadData()方法读取图片数据,然后在行调用decodeFromSourceData()方法来对图片进行解码。接下来会先判断是否允许缓存原始图片(cacheSource()),如果允许的话又会调用cacheAndDecodeSourceData()方法。而在这个方法中同样调用了getDiskCache()方法来获取DiskLruCache实例,接着调用它的put()方法就可以写入硬盘缓存了,注意原始图片的缓存Key是用的resultKey.getOriginalKey()。

而转换的图片保存呢,可以猜到发生transformEncodeAndTranscode(decoded)中:

private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
    long startTime = LogTime.getLogTime();
    Resource<T> transformed = transform(decoded);
    writeTransformedToCache(transformed);
    startTime = LogTime.getLogTime();
    Resource<Z> result = transcode(transformed);
    return result;
}

private void writeTransformedToCache(Resource<T> transformed) {
    if (transformed == null || !diskCacheStrategy.cacheResult()) {
        return;
    }
    long startTime = LogTime.getLogTime();
    SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
    diskCacheProvider.getDiskCache().put(resultKey, writer);
}

先是调用transform()方法来对图片进行转换显示的ImageView尺寸,然后在writeTransformedToCache()方法中将转换过后的图片写入到硬盘缓存中,如果cacheResult通过的话,调用的同样是DiskLruCache实例的put()方法,不过这里用的缓存Key是resultKey。

最后 :到这里这样我们就将Glide硬盘缓存的实现原理也分析完了。我只需要通过skipMemoryCache()和diskCacheStrategy()这两个方法就控制缓存策略了。

参考内容:
http://blog.csdn.net/guolin_blog/article/details/54895665
http://blog.csdn.net/ss8860524/article/details/50668118对象复用

猜你喜欢

转载自blog.csdn.net/u010019468/article/details/79032452