glide的内存管理和缓存机制

glide生命周期管理、内存管理、缓存机制、加载器、观察者模式

前言:本系列博文主要通过借鉴其他优秀博文以及自己的实际开发经验综合得出,主要分析glide相关使用、机制以及设计模式等内容,相信这篇博文会让你对glide有更加深入的理解。本篇是系列文章的第三章


篇三  glide的内存管理和缓存机制

前言:本篇博客将深入浅出地介绍glide的内存管理机制,主要借鉴来源:https://www.jianshu.com/p/17644406396b


一、Glide加载图片整体流程了解:

  • Glide:Glide除了是接口封装类,还负责创建全局使用的工具和组件。
  • GenericRequest:为每个图片加载创建一个Request,初始化这张图片的转码器、图片变换器、图片展示器target等,当然这个过程实在GenericRequestBuilder的实现类里完成的。
  • Engine:异步处理总调度器。EnginJob负责线程管理,EngineRunnable是一个异步处理线程。DecodeJob是真正线程里获取和处理图片的地方。
  • HttpUrlFetcher :获取网络流,使用的是HttpURLConnection。
  • Decoder :读取网络流后,解码得到Bitmap或者gifResource。因为加载图片类型不同,这快分支较多,学习Glide初级阶段,这块可以先不再详细分析。

接下来我们进入主题,缓存的代码在上面流程图里的什么位置?

二、Glide的缓存机制(二级缓存)

Glide内存缓存源码分析:

内存缓存的读存都在Engine类中完成。

Glide内存缓存的特点:

内存缓存使用弱引用LruCache结合完成的,弱引用来缓存的是正在使用中的图片。图片封装类Resources内部有个计数器判断是该图片否正在使用。

  • 读:是先从lruCache取,取不到再从弱引用中取;
  • 存:内存缓存取不到,从网络拉取回来先放在弱引用里,渲染图片,图片对象Resources使用计数加一;
  • 渲染完图片,图片对象Resources使用计数减一,如果计数为0,图片缓存从弱引用中删除,放入lruCache缓存。

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

        final String id = fetcher.getId();
        //生成缓存的key
        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;
        }
        //从弱引用获取图片
        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;
        }

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

    ...
}

我们具体看取图片的两个方法loadFromCache()和loadFromActiveResources()。loadFromCache使用的就是LruCache算法,loadFromActiveResources使用的就是弱引用。我们来看一下它们的源码:

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

    private final MemoryCache cache;
    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
    ...

    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }
        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
            cached.acquire();
            activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
        }
        return cached;
    }

    private EngineResource<?> getEngineResourceFromCache(Key key) {
        Resource<?> cached = cache.remove(key);
        final EngineResource result;
        if (cached == null) {
            result = null;
        } else if (cached instanceof EngineResource) {
            result = (EngineResource) cached;
        } else {
            result = new EngineResource(cached, true /*isCacheable*/);
        }
        return result;
    }

    private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }
        EngineResource<?> active = null;
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
            active = activeRef.get();
            if (active != null) {
                active.acquire();
            } else {
                activeResources.remove(key);
            }
        }
        return active;
    }

    ...
}

loadFromCache()方法:

  • 首先就判断isMemoryCacheable是不是false,如果是false的话就直接返回null。这就是skipMemoryCache()方法设置的是否内存缓存已被禁用。
  • 然后调用getEngineResourceFromCache()方法来获取缓存。在这个方法中,会从中获取图片缓存LruResourceCache,LruResourceCache其实使用的就是LruCache算法实现的缓存。
  • 当我们从LruResourceCache中获取到缓存图片之后会将它从缓存中移除,将缓存图片存储到activeResources当中。activeResources就是弱引用的HashMap,用来缓存正在使用中的图片。

loadFromActiveResources()方法:

  • 就是从activeResources这个activeResources当中取值的。使用activeResources来缓存正在使用中的图片,用来保护正在使用中的图片不会被LruCache算法回收掉。

这样我们把从内存读取图片缓存的流程搞清了,那是什么时候存储到activeResources中的呢?是不是应该在异步处理获取到图片后,再缓存到内存?EngineJob 获取到图片后会回调Engine的onEngineJobComplete()。我们来看下做了什么:

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

    ...
}

在onEngineJobComplete()方法里将正在加载的图片放到弱引用缓存。那什么时候放在LruCache里呢?当然是在使用完,那什么时候使用完呢?

那我们来看EngineResource这个类是怎么标记自己是否在被使用的。EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,代码如下所示:

class EngineResource<Z> implements Resource<Z> {

    private int acquired;
    ...

    void acquire() {
        if (isRecycled) {
            throw new IllegalStateException("Cannot acquire a recycled resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
            throw new IllegalThreadStateException("Must call acquire on the main thread");
        }
        ++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);
        }
    }
}

可以看出当引用计数acquired变量为0,就是没有在使用了,然后调用了 listener.onResourceReleased(key, this);
这个listener就是Engine对象,我们来看下它的onResourceReleased()方法:

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

    private final MemoryCache cache;
    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
    ...    

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource resource) {
        Util.assertMainThread();
        activeResources.remove(cacheKey);
        if (resource.isCacheable()) {
            cache.put(cacheKey, resource);
        } else {
            resourceRecycler.recycle(resource);
        }
    }

    ...
}

Glide磁盘缓存源码分析

具体看源码:
源码入口位置是在EngineRunnable的run()方法,run()方法中调用到decode()方法,decode()方法的源码:

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

处理后的缓存图和原图缓存图对应的是DiskCacheStrategy.RESULT和DiskCacheStrategy.SOURCE这两个缓存模式。
到DecodeJob具体看下这两个读取磁盘缓存的方法,decodeResultFromCache()和decodeSourceFromCache():

public Resource<Z> decodeResultFromCache() throws Exception {
    if (!diskCacheStrategy.cacheResult()) {
        return null;
    }
    long startTime = LogTime.getLogTime();
    Resource<T> transformed = loadFromCache(resultKey);
    startTime = LogTime.getLogTime();
    Resource<Z> result = transcode(transformed);
    return result;
}

public Resource<Z> decodeSourceFromCache() throws Exception {
    if (!diskCacheStrategy.cacheSource()) {
        return null;
    }
    long startTime = LogTime.getLogTime();
    Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
    return transformEncodeAndTranscode(decoded);
}

这里两个方法都先判断了是否是对应的缓存模式,不是则读取失败。这里我们不关注transform 和transcode的相关功能,只分析缓存功能。两个缓存方法都调用到了loadFromCache()方法,只是传入的key不同。一个是处理后图片的key,一个是原始图片的key。
继续看loadFromCache()方法的源码:

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

源码中可以看到我们是从diskCacheProvider.getDiskCache()中读取的缓存,diskCacheProvider.getDiskCache()获得的是DiskLruCache工具类的实例,然后从DiskLruCache获取缓存。之后的decode不是本篇的关注点,先不分析。
到这里我们把从磁盘缓存读取缓存的流程讲完了,那什么时候存入的呢?肯定是在从原始位置获取图片后,我们回到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;
}

decodeSource()方法中获取图片后,调用到decodeFromSourceData()方法,然后判断是否缓存原图,是的话就调用到cacheAndDecodeSourceData(A data)方法。看进去,还是调用了 diskCacheProvider.getDiskCache()获取DiskLruCache工具类的实例。然后调用put方法缓存了原图。

到此我们缓存了原图,处理后的图片是什么时候缓存的?肯定是在图片处理之后,在transformEncodeAndTranscode()方法中:

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

transformEncodeAndTranscode中先对图片进行了转换,然后调用writeTransformedToCache(transformed);判断是否缓存处理后的图片,是就对处理后的图片进行了缓存。调用的同样是DiskLruCache实例的put()方法,不过这里用的缓存Key是resultKey。
至此图片磁盘缓存都讲解完了,对照源码看下之前的Glide磁盘缓存流程是不是清晰了很多。


总结

本篇我们主要讲解了Glide的二级缓存机制,虽然代码比较长,但是基本流程比较清晰,大家通过对流程的梳理,加深对Glide缓存机制的理解。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值