Glide缓存机制

前言
本文基于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 = https://www.it610.com/article/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选择将原始图片先写入硬盘缓存,然后再从硬盘缓存中加载图片。如果没有开启原始图片缓存,则直接解码原始数据。
【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); }

    推荐阅读