Glide系列(四) — Glide缓存流程分析

一、概述

1.1 背景

Glide 是一个图片加载库,跟它同类型的库还有 Picasso、Fresco、Universal-Image-Loader 等。基于 Glide 优秀的缓存管理策略和生命周期关联的特点,目前市面上对 Glide 的使用非常广,因此我们有必要深入研究下 Glide 相关的实现原理,便于更好的使用它。

在前面三篇文章中,我们介绍了 Glide 框架的整体结构,以及 Glide 中关于 DataLoaderProviderModelLoaderResourceTranscoder 注册部分的逻辑。本文我们主要来分析一下 Glide 中5层缓存的设计。

Glide 版本: Glide 3.7.0
Github 地址: https://github.com/bumptech/glide/tree/v3.7.0
Gradle 依赖: implementation 'com.github.bumptech.glide:glide:3.7.0'

1.2 系列文章

  1. Glide系列(一) — Glide 框架结构浅析
  2. Glide系列(二) — DataLoadProvider 及与 Encoder&Decoder 的关系
  3. Glide系列(三) — LoadProvider、ModelLoader、DataFetcher 和 ResourceTranscoder 关系
  4. Glide系列(四) — Glide 缓存流程分析
  5. DiskLruCache 源码分析

二、准备知识

在分析 Glide 缓存结构之前,我们先回顾下 Glide系列(一) — Glide 框架结构浅析 中提到的 Glide 的整体分层结构。

2.1 Glide 的缓存分层结构

在这里插入图片描述

小结:

  • Glide 的整体结构与网络请求类似,都分为请求和响应两部分。
  • Glide 框架主要分为两大流程:
    • 图片请求的构建流程。
    • 图片缓存的获取流程。
  • 图片缓存一共分为3大层,5小层:
    • 内存缓存(2小层):弱引用缓存、LruCache。
    • 本地缓存(2小层):本地 ResultCache 缓存、本地 SourceCache 缓存,通过 DiskLruCache 进行文件管理。
    • Source 数据源(1小层):网络获取、本地 AssetPath 获取、其它本地图片。

2.2 Glide 缓存相关类的关联关系

下图展示了缓存相关类的关联关系,以及个数的对应关系。

在这里插入图片描述
小结:

  • Engine: 是个单例,负责具体执行内存缓存获取逻辑,并触发磁盘缓存的获取流程 (磁盘缓存的获取流程其实是由内部的 EngineJob 和 EngineRunnable 代理)。
  • ActiveResources: 第一层缓存(内存缓存),主要缓存的是屏幕正在使用中的图片,它使用的是弱引用进行缓存,它与Engine是一对一的关系。这里就有一个面试题,为什么要使用弱引用缓存,强引用可以吗,为什么?
  • LruResourceCache: 第二层缓存(内存缓存),主要是缓存ActiveResources中引用计数为0的数据,它与Engine是一对一的关系。
  • EngineJob: 每个图片请求都对应一个 EngineJob,它主要负责从磁盘获取缓存后将数据回传给Request的逻辑。它对Engine是多对一的关系。
  • EngineRunnable: 是一个获取缓存的任务,它与 EngineJob 是一对一的关系。
  • DecodeJob: 主要功能是负责从第三层到第5层缓存的获取并进行解码,在 EngineRunnable 中会执行 DecodeJob 对应的方法进行缓存的获取。内部含5部分能力。
    • 有提供编解码能力的 DataLoadProvider。
    • 提供数据源获取能力的 DataFetcher (第五层缓存)。
    • 提供数据处理的 Transformation。
    • 提供数据类型变换的 ResourceTranscoder。
    • 提供获取缓存文件能力的 DiskCacheProvider。
  • DiskCache:负责管理缓存文件的存/取 (第三、四层缓存)。

三、缓存的获取流程

上面我们介绍了缓存的层级,以及与缓存相关的几个类之间的关联关系,下面我们从源码的角度来分析一下这一过程。

主要从下面几个角度分析:

  • 缓存获取的入口。
  • 内存缓存的获取。
  • 磁盘缓存的获取。

3.1 缓存获取的入口

前面我们提到,Glide 获取图片的操作类似网络请求,下面我们就先来分析下缓存获取的入口。

// GenericRequest.class
public void onSizeReady(int width, int height) {
    status = Status.RUNNING;
    width = Math.round(sizeMultiplier * width);
    height = Math.round(sizeMultiplier * height);
	// 获取ModelLoader
    ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
    // 获取 DataFetcher
    final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
    // 获取 ResourceTranscoder
    ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();

    loadedFromMemoryCache = true;
    // 调用 Engine.load() 方法执行缓存加载逻辑。
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
            priority, isMemoryCacheable, diskCacheStrategy, this);
}

小结:

  • 在调用 Engine.load() 方法之前,就从注册表里找到了与加载数据相关的 ModelLoader、DataFetcher、ResourceTranscoder
  • 此处的 loadProvider 是 FixedLoadProvider 类型,为什么不直接传入 load 方法内?
    • load() 方法接收 DataLoadProvider 类型参数,遵循类的单一职责。
  • load() 方法传入的最后一个参数类型为 ResourceCallback,它是 Request 与 Engine 关联的纽带。

3.2 内存缓存的查询

前面我们已经提到了获取缓存的入口,下面我们分析一下 Engine.load() 方法具体做了些什么。

// Engine.class
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) {
	// 1.生成EngineKey,它用于第1,2,3层缓存的查找。里面包含了所需图片的尺寸、解码器等信息。
    final String id = fetcher.getId();
    EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
            loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
            transcoder, loadProvider.getSourceEncoder());
	// 2.先从第2层缓存中查找。
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        cb.onResourceReady(cached);
        return null;
    }
	// 2.再从第1层缓存中查找。
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
        cb.onResourceReady(active);
        return null;
    }
	
	// jobs 是一个Map集合,维护了当前正在执行数据请求的任务。当需要取消所有任务时,方便操作。
	// 3.如果同一个图片被请求两次,会将第二次的请求直接关联到第1次的请求上,避免重复请求。
    EngineJob current = jobs.get(key);
    if (current != null) {
        current.addCallback(cb);
        return new LoadStatus(cb, current);
    }
	// 4.执行磁盘缓存查询逻辑。
    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);
    // 这里启动EngineRunnable任务。
    engineJob.start(runnable);

    return new LoadStatus(cb, engineJob);
}

// 第二层内存缓存查找逻辑
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    //...
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
        cached.acquire();
        // 缓存到第一层activeResources内存缓存中。
        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);
    }
    return result;
}

// 第一层内存缓存查找逻辑
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    //...
    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;
}

小结:

  • Engine.load() 方法主要处理了2层内存缓存的查询逻辑。先从第二层内存缓存中查询,再从第一层内存缓存中查询。
  • 如果从第二层内存缓存中找到缓存后,要将缓存从第二层缓存移除,并加入到第一层缓存中。
  • 第一层缓存使用弱应用进行缓存,便于当弱引用中的数据没有被使用,且仍缓存在第一层缓存中时被回收。如果使用强引用代替,则当该缓存资源没有被使用时,占用的内存资源也不能被释放。
  • Engine 中通过一个 Map 集合缓存当前正在执行的 EngineJob。当需要取消所有任务时,方便操作。
  • 缓存 EngineJob 的另一个好处是:如果同一个图片被请求两次,会将第二次的请求直接关联到第1次的EngineJob 上,避免重复请求。
  • 执行后面三层磁盘缓存查询逻辑。

3.2 磁盘缓存的查询

上面介绍了磁盘缓存的查询流程具体交给 EngineJob 和 EngineRunnable来执行,下面我们就来分析一下。

// Engine.load 方法
// EngineJob 主要是负责图片缓存获取到之后的逻辑处理,如回传给Request。
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是具体执行加载逻辑的类,所以要将EngineJob传入,方便将获取到的缓存数据回传给EngineJob,继而传递到Request中。
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
// 里面是一个线程池,用于执行EngineRunnable任务。
engineJob.start(runnable);

小结:

  • EngineJob 内部持有两个线程池。
    • DiskCacheService 线程池:用于 ResultCache、SourceCache 的缓存加载解析。
    • SourceSrevice 线程池:用于 Source 数据源的加载解析。
  • EngineRunnable 内传入 EngineJob:EngineJob 主要负责缓存获取后的逻辑处理,并与 Request 通信。EngineRunnable 是具体执行缓存加载逻辑的类,所以要将 EngineJob 传入,是方便获取到缓存时通过 EngineJob 传递到 Request 中。

下面我们重点看下 EngineRunnable.run() 方法。

// EngineRunnable.class
public void run() {
    //...
    Resource<?> resource = null;
    try {
    	// 1.获取缓存数据并使用Decoder进行解析。
        resource = decode();
    } catch (Exception e) {
    }
    //...
    if (resource == null) {
    	// 数据获取失败
        onLoadFailed(exception);
    } else {
    	// 数据获取成功
        onLoadComplete(resource);
    }
}

// stage 默认是Stage.CACHE。
this.stage = Stage.CACHE;
private boolean isDecodingFromCache() {
    return stage == Stage.CACHE;
}

private Resource<?> decode() throws Exception {
	// 是否是从磁盘缓存中解析,默认是true。
    if (isDecodingFromCache()) {
        return decodeFromCache();
    } else {
        return decodeFromSource();
    }
}
    
private void onLoadFailed(Exception e) {
    if (isDecodingFromCache()) {
    	// 如果当stage为Stage.CACHE时,走到onLoadFailed逻辑,说明磁盘缓存里没有数据,因此将stage设置为Stage.SOURCE,并重新执行EngineRunnable.run()方法。
        stage = Stage.SOURCE;
        // SourceService 线程池
        manager.submitForSource(this);
    } else {
        manager.onException(e);
    }
}

private void onLoadComplete(Resource resource) {
    manager.onResourceReady(resource);
}

// ----------------  Another Class --------------------
// EnginJob.class
public void onResourceReady(final Resource<?> resource) {
    this.resource = resource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}

private void handleResultOnMainThread() {
	//...
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;
    engineResource.acquire();
    // 1.保存到第一层内存缓存
    listener.onEngineJobComplete(key, engineResource);
    for (ResourceCallback cb : cbs) {
        if (!isInIgnoredCallbacks(cb)) {
            engineResource.acquire();
            // 2.回传给Request的Callback
            cb.onResourceReady(engineResource);
        }
    }
    engineResource.release();
}

小结:

  • EngineRunnable.run() 方法的流程比较简单,主要分为2个步骤:
    1. 查询缓存并进行 decode 解析。
    2. 判断返回值是否存在,不存在就执行 onLoadFailed 逻辑,成功就执行 onLoadComplete 逻辑。
  • isDecodingFromCache() 方法用于判断当前解析的数据是来自缓存还是来自第五次缓存(数据源)。
  • onLoadFailed() 方法:当stage为Stage.CACHE时,触发了onLoadFailed逻辑,说明磁盘缓存里没有数据,因此将stage设置为Stage.SOURCE,并重新执行EngineRunnable.run()方法,因此会从第五次缓存(数据源)查找数据。
  • onLoadComplete() 方法:decode() 方法获取到数据后,会执行 onLoadComplete 方法。该方法内的 manager 为 EnginJob 对象,所以会调用 EnginJob.onResourceReady() 方法将数据回传到 Request。
  • EngineRunnable 里的 stage 有点像状态机,通过为stage设置不同状态并重新触发EngineRunnable任务来实现。

接下来我们重点看下 EngineRunnable.decodeFromCache()、EngineRunnable.decodeFromSource 两个方法。

Condition1:ResultCache 缓存

EngineRunnable.decodeFromCache()

// EngineRunnable.class
private Resource<?> decodeFromCache() throws Exception {
    Resource<?> result = null;
    try {	
    	//1.先从ResultCache获取缓存。
        result = decodeJob.decodeResultFromCache();
    } catch (Exception e) {
    }
    if (result == null) {
    	//2.再从SourceCache获取缓存。
        result = decodeJob.decodeSourceFromCache();
    }
    return result;
}

小结:

  • 磁盘缓存分为 ResultCache、SourceCache 两部分,所以这里的逻辑也分两步来执行。
    • ResultCache:经过 transform 处理之后的数据。
    • SourceCache:从 Source 加载的数据,未经过任何处理。
  • EngineRunnable.decodeFromCache() 方法会先从 ResultCache 中获取,如果没有缓存,再从SourceCache中获取缓存。

DecodeJob.decodeResultFromCache()

public Resource<Z> decodeResultFromCache() throws Exception {
    if (!diskCacheStrategy.cacheResult()) {
        return null;
    }
    // 1.从DiskLruCache中获取ResultCache缓存,这里的key为EngineKey。
    Resource<T> transformed = loadFromCache(resultKey);
    // 2.将Resource<T>类型的数据transformed转码变成另一个类型(Resource<Z>)的数据result。
    // transcode()方法内调用transcoder.transcode(transformed)。
    Resource<Z> result = transcode(transformed);
    return result;
}

private Resource<T> loadFromCache(Key key) throws IOException {
	// 1.1 从DiskLruCache中获取缓存。
    File cacheFile = diskCacheProvider.getDiskCache().get(key);
    if (cacheFile == null) {
       return null;
    }

    Resource<T> result = null;
    try {
    	// 1.2 通过解码器对缓存文件进行解码。
       result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
    } finally {
       if (result == null) {
           diskCacheProvider.getDiskCache().delete(key);
       }
    }
    return result;
}

小结:

  1. loadFromCache(resultKey) 方法:从DiskLruCache中获取 ResultCache 缓存,这里的 key 为 EngineKey。
    • 1.1 通过 DiskLruCache 获取 ResultCache 缓存文件。
    • 1.2 通过解码器对缓存文件进行解码。
  2. transcode() 方法:将Resource类型的数据转码变成另一个类型(Resource)的数据。

Condition2:SourceCache 缓存

DecodeJob.decodeSourceFromCache()

// DecodeJob.class
public Resource<Z> decodeSourceFromCache() throws Exception {
    if (!diskCacheStrategy.cacheSource()) {
        return null;
    }
    // 1.从DiskLruCache中获取SourceCache缓存,这里的key为OriginalKey,
    // 这部分前面已经分析过了。
    Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
    // 2.执行transform、Encoder、Transcode操作。
    return transformEncodeAndTranscode(decoded);
}

private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
	// 2.1 执行transform操作。我们外部传入的transform就是在这里执行的。
    Resource<T> transformed = transform(decoded);
    // 2.2 将内存数据写入的磁盘中,生成ResultCache缓存。
    writeTransformedToCache(transformed);
    // 2.3 将Resource<T>类型的数据转码变成另一个类型(Resource<Z>)的数据,这部分前面已经分析过了。
    Resource<Z> result = transcode(transformed);
    return result;
}

private void writeTransformedToCache(Resource<T> transformed) {
	//...
	// 将内存数据写入的磁盘中,生成ResultCache缓存。
    SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
    diskCacheProvider.getDiskCache().put(resultKey, writer);
}

小结:

  1. 从 DiskLruCache 中获取 SourceCache 缓存,这里的 key 为 OriginalKey。
    • 1.1 通过 DiskLruCache 获取 SourceCache 缓存文件。
    • 1.2 通过解码器对缓存文件进行解码。
  2. 执行transform、Encoder、Transcode操作。
    • 2.1 执行transform操作。我们外部传入的transform就是在这里执行的。
    • 2.2 将内存数据写入的磁盘中,生成ResultCache缓存。
    • 2.3 将Resource类型的数据transformed转码变成另一个类型(Resource)的数据result。
  3. 关于 DiskLruCache 的内容可以参考:DiskLruCache 源码分析

Condition3:Source 缓存

下面我们分析最后一种常见,直接从数据源加载数据。

EngineRunnable.decodeFromSource

// EngineRunnable.class
private Resource<?> decodeFromSource() throws Exception {
	// 从Source数据源获取缓存
    return decodeJob.decodeFromSource();
}

// ----------------  Another Class --------------------
// DecodeJob.class
public Resource<Z> decodeFromSource() throws Exception {
	// 1.加载并解析数据源(加载数据+对数据解码+SourceCache缓存)。
    Resource<T> decoded = decodeSource();
    // 2.执行transform、Encoder、Transcode操作。
    return transformEncodeAndTranscode(decoded);
}

private Resource<T> decodeSource() throws Exception {
    Resource<T> decoded = null;
    try {
    	// 1.1 使用DataFetcher加载数据源数据。
        final A data = fetcher.loadData(priority);
        if (isCancelled) {
            return null;
        }
        // 1.2 将数据到SourceCache缓存层,并重新触发第4层缓存的查找逻辑。
        decoded = decodeFromSourceData(data);
    } finally {
        fetcher.cleanup();
    }
    return decoded;
}


private Resource<T> decodeFromSourceData(A data) throws IOException {
    final Resource<T> decoded;
    if (diskCacheStrategy.cacheSource()) {
    	// 使用 DiskLruCache 进行数据缓存,并重新触发从第4层缓存查找逻辑。
        decoded = cacheAndDecodeSourceData(data);
    } else {
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
    }
    return decoded;
}

private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
	// 1.2.1 使用 DiskLruCache 进行数据缓存。
    SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
    diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
    // 1.2.2 执行第4层缓存的查找逻辑(前面已经介绍过)。
    Resource<T> result = loadFromCache(resultKey.getOriginalKey());
    return result;
}

小结:

  1. 加载并解析数据源(加载数据+对数据解码+SourceCache缓存)。
    • 1.1 使用DataFetcher加载数据源数据。
    • 1.2 将数据到SourceCache缓存层,并重新触发第4层缓存的查找逻辑。
      • 1.2.1 使用 DiskLruCache 进行数据缓存 (缓存到4级磁盘缓存)。
      • 1.2.2 执行第4层缓存的查找逻辑:根据OriginalKey从 DiskLruCache 中查找缓存文件,并进行解码。
  2. 执行transform、Encoder、Transcode操作。
    • 2.1 执行transform操作。我们外部传入的transform就是在这里执行的。
    • 2.2 将内存数据写入的磁盘中,生成ResultCache缓存 (缓存到3级磁盘缓存)。
    • 2.3 将Resource类型的数据transformed转码变成另一个类型(Resource)的数据result。

3.3 缓存查询流程图

到这里,5种缓存的获取流程就分析结束了,下面我们看下这5种获取缓存场景的流程图。
在这里插入图片描述

小结:

  • 蓝色部分为缓存查询流程。
  • 黄色部分为缓存的保存流程。
  • 从第五层缓存数据写入第四层缓存后,会重新触发第4层缓存的查询逻辑。

四、总结

  1. 了解 Glide 的缓存分层结构,及与 Request 是如何关联的。
  2. 了解缓存的查询流程,特别是要注意优先查询第二层内存缓存。
  3. 了解缓存的保存逻辑。
    • 第二层内存缓存 -> 第一次内存缓存
    • 第五层数据源缓存 -> 第四层磁盘缓存 -> 第一次内存缓存
    • 第四层磁盘缓存 -> 第三层磁盘缓存 -> 第一次内存缓存
  4. 了解缓存查询相关类的关联关系,及其作用。
  5. 了解缓存的数据变换操作 (ResourceTranscoder)。

五、碎碎念

到这里,Glide 的核心功能就已经分析完了,剩余的话题有空再写吧。诸如:

  1. 生命周期关联。
  2. Module 的自定义。
  3. 缩略图与原图加载的协同原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值