Glide缓存非常先进,很灵活,很全面,总体上来讲有内存缓存和磁盘文件缓存。缓冲机制概括来讲就是读缓存以及是写入缓存的机制。而Glide读缓存时机就是先内存缓存查找再到磁盘缓存查找最后网络,写入缓存则就是在获取到原始source图片之后,先写入磁盘缓存,再加入内存缓存。
每个缓存查找都是通过key来查询,一般都是直接用下载url来作为key,那Glide的key呢。直接看下Engine .load源码作为文章开头概述:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
......
public LoadStatus load(Key signature, int width, int height, DataFetcher fetcher,
DataLoadProvider loadProvider, Transformation transformation, ResourceTranscoder 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 decodeJob = new DecodeJob(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();
}
}
如果有缓存的话,会读取文件缓存,先看下读取缓存。
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 loadFromCache(Key key) throws IOException {
File cacheFile = diskCacheProvider.getDiskCache().get(key);
if (cacheFile == null) {
return null;
}
Resource result = null;
try {
result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
} finally {
if (result == null) {
diskCacheProvider.getDiskCache().delete(key);
}
}
return result;
}
读取缓存就到这里,接下来看下如何写入磁盘缓存
从上文中看到先读取缓存,没有才decodeFromSource获取源头原始数据,那么在获取网络数据之后会不会缓存呢,这是最佳时候。
public Resource decodeFromSource() throws Exception {
Resource decoded = decodeSource();
return transformEncodeAndTranscode(decoded);
}
这个方法中decodeSource()顾名思义是用来解析原图片的,而transformEncodeAndTranscode()则是用来对图片进行转换和转码的。我们先来看decodeSource()方法做了什么:
private Resource decodeSource() throws Exception {
Resource 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 decodeFromSourceData(A data) throws IOException {
final Resource decoded;
if (diskCacheStrategy.cacheSource()) {
decoded = cacheAndDecodeSourceData(data);
} else {
long startTime = LogTime.getLogTime();
decoded = loadProvider.getSourceDecoder().decode(data, width, height);
}
return decoded;
}
private Resource cacheAndDecodeSourceData(A data) throws IOException {
long startTime = LogTime.getLogTime();
SourceWriter writer = new SourceWriter(loadProvider.getSourceEncoder(), data);
diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
startTime = LogTime.getLogTime();
Resource result = loadFromCache(resultKey.getOriginalKey());
return result;
}
可以看到先调用fetcher的loadData()方法读取图片数据,然后在行调用decodeFromSourceData()方法来对图片进行解码。接下来会先判断是否允许缓存原始图片(cacheSource()),如果允许的话又会调用cacheAndDecodeSourceData()方法。而在这个方法中同样调用了getDiskCache()方法来获取DiskLruCache实例,接着调用它的put()方法就可以写入硬盘缓存了,注意原始图片的缓存Key是用的resultKey.getOriginalKey()。
而转换的图片保存呢,可以猜到发生transformEncodeAndTranscode(decoded)中:
private Resource transformEncodeAndTranscode(Resource decoded) {
long startTime = LogTime.getLogTime();
Resource transformed = transform(decoded);
writeTransformedToCache(transformed);
startTime = LogTime.getLogTime();
Resource result = transcode(transformed);
return result;
}
private void writeTransformedToCache(Resource transformed) {
if (transformed == null || !diskCacheStrategy.cacheResult()) {
return;
}
long startTime = LogTime.getLogTime();
SourceWriter> writer = new SourceWriter>(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对象复用