前言
本文基于Glide v3.7.0源码分析,Glide v4.0大致流程和v3.7.0差不多,在一些技术细节上有修改。主要内容有:
- 内存缓存读取
- 内存缓存写入
- 缓存引用计数
- 硬盘缓存读取
- 硬盘缓存写入
内存缓存读取
内存缓存相关代码主要在Engine.java中
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();
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
// 从LruCache中寻找缓存
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;
}
// LruCache中没有找到缓存,从弱引用中寻找缓存
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;
}
...
}
两级内存缓存:先从LruCache中寻找,如果找到了缓存,将图片移出LruCache,加入activeResources弱引用缓存。如果在LruCache中没找到的话到activeResources弱引用缓存中寻找。如果在内存缓存中找到,则引用计数加1。使用中的图片用弱引用缓存来管理,没有使用的图片用LruCache来管理,判断图片有没有使用的依据之一是引用计数,当引用计数等于0时,将图片从弱引用缓存中移走,加入LruCache中。
内存缓存写入
图片会先写入到activeResources弱引用缓存中。
@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);
}
当引用计数为0的时候会将图片放到LruCache中。
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
缓存引用计数
这里其实就有个问题了,当我们滑动图片列表的时候,被回收的View中的图片自然也用不到了,我们并没有人为的去把计数器减1,那么Glide又是怎么知道图片已经没有被引用了,从而将它放到LruCache中的呢?其实Glide并不知道,但activeResources包含的值是图片资源的弱引用。
private final Map>> activeResources;
当滑动图片列表的时候,系统会根据需要将这些图片资源给回收掉,所以activeResources.get(key).get()
得到的就会为null,为null也没有必要添加到LruCache中了。
但是这样子一来,activeResources中就会有很多没有用的item了,而它们又没有被移除掉。为了解决这个问题,Glide用了MessageQueue.IdleHandler
这个利器来解决这个问题。
(IdleHandler也可以用来解决App启动延时加载的问题,具体可以看Android 启动优化之延时加载)
private static class RefQueueIdleHandler implements MessageQueue.IdleHandler {
private final Map>> activeResources;
private final ReferenceQueue> queue;
public RefQueueIdleHandler(Map>> activeResources,
ReferenceQueue> queue) {
this.activeResources = activeResources;
this.queue = queue;
}
@Override
public boolean queueIdle() {
ResourceWeakReference ref = (ResourceWeakReference) queue.poll();
if (ref != null) {
activeResources.remove(ref.key);
}
return true;
}
}
在Glide v4.0版本,Glide不是用IdleHandler来解决这个问题的了,而是开了一个线程优先级为Process.THREAD_PRIORITY_BACKGROUND
的线程,然后再通过MainHandler发送消息到主线程中去处理的,技术实现不同,但目的是一样的。
硬盘缓存读取
当从两级内存缓存中都获取不到图片的时候,会开启线程,尝试从硬盘中获取缓存。根据硬盘缓存策略,可以只缓存转换过后的图片,也可以缓存原始图片。所以从硬盘中获取缓存的时候,会有两种方法,分别对应DiskCacheStrategy.RESULT和DiskCacheStrategy.SOURCE。相关代码在DecodeJob.java中。
private Resource> decodeFromCache() throws Exception {
Resource> result = null;
try {
// DiskCacheStrategy.RESULT
result = decodeJob.decodeResultFromCache();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Exception decoding result from cache: " + e);
}
}
if (result == null) {
// DiskCacheStrategy.SOURCE
result = decodeJob.decodeSourceFromCache();
}
return result;
}
硬盘缓存写入
在没有任何缓存的情况下,会解码原始图片
public Resource decodeFromSource() throws Exception {
// 解码原始数据
Resource decoded = decodeSource();
// 根据需要转换图片
return transformEncodeAndTranscode(decoded);
}
缓存原始图片:
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;
}
可以看到,开启了原始图片缓存的情况下,Glide选择将原始图片先写入硬盘缓存,然后再从硬盘缓存中加载图片。如果没有开启原始图片缓存,则直接解码原始数据。
缓存结果图片:
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);
}