Glide缓存机制

前言

本文基于Glide v3.7.0源码分析,Glide v4.0大致流程和v3.7.0差不多,在一些技术细节上有修改。主要内容有:

  • 内存缓存读取
  • 内存缓存写入
  • 缓存引用计数
  • 硬盘缓存读取
  • 硬盘缓存写入

内存缓存读取

内存缓存相关代码主要在Engine.java中

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

    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<Key, WeakReference<EngineResource<?>>> 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<Key, WeakReference<EngineResource<?>>> activeResources;
    private final ReferenceQueue<EngineResource<?>> queue;

    public RefQueueIdleHandler(Map<Key, WeakReference<EngineResource<?>>> activeResources,
            ReferenceQueue<EngineResource<?>> 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<Z> decodeFromSource() throws Exception {
    // 解码原始数据
    Resource<T> decoded = decodeSource();
    // 根据需要转换图片
    return transformEncodeAndTranscode(decoded);
}

缓存原始图片:

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

可以看到,开启了原始图片缓存的情况下,Glide选择将原始图片先写入硬盘缓存,然后再从硬盘缓存中加载图片。如果没有开启原始图片缓存,则直接解码原始数据。

缓存结果图片:

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);
}
    原文作者:ETF大战Android
    原文地址: https://www.jianshu.com/p/7b1ff697b06f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞