0
点赞
收藏
分享

微信扫一扫

Glide load源码、缓存机制分析


我们知道Glide链式调用最重要的就是​​into()​​​,而 into()最终会调用 ​​load()​​。

1.load源码分析

1. load流程

我们来分析一下 ​​load()​​做了什么事情。

public <R> LoadStatus load(...) {
Util.assertMainThread();
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
...

首先通过工厂模式创建了一个 ​​EngineKey​​对象key。 看下这个类:

// EngineKey.java
class EngineKey implements Key {
EngineKey(
Object model,
Key signature,
int width,
int height,
Map<Class<?>, Transformation<?>> transformations,
Class<?> resourceClass,
Class<?> transcodeClass,
Options options) {
... // setter
}

@Override
public boolean equals(Object o) {
...
}

@Override
public int hashCode() {
...
}
}

它的构造函数是赋值,并重写了 ​​equals()​​​和​​hashCode()​​​,除此之外没啥别的函数了。
可以认为它的作用是描述一个Engine,重写eqauls和hashCode的作用还没看出来,但是可以猜想,是便于后续的复用。

load() 接下来走:

// load
...
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
...

调用了 ​​loadFromActiveResources()​​​,传入了之前的生成的key。如果得到一个 ​​EngineResource​​​对象,则回调 ​​onResourceReady()​​​, onResourceReady是这个加载图片的出口。来看下 ​​loadFromActiveResources()​​做了啥:

// Engine.java
private final ActiveResources activeResources;

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

return active;
}

第一个if是如果 isMemoryCacheable为false则返回null,从字面意思上可以知道这个字段的意思是 是否开启内存缓存
然后调用 ​​​EngineResource<?> active = activeResources.get(key)​​,它是通过key去ActiveResources里面取一个 EngineResource:

// ActiveResources.java
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

@Nullable
EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key); // 1
if (activeRef == null) {
return null;
}

EngineResource<?> active = activeRef.get(); // 2
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}

从注释1、2可以看到,ActiveResources维护一个HashMap,Key为 ​​Key​​​类,我们刚刚的​​EngineKey​​​就是Key的派生类,Value为 Resources的弱引用。
也就是说,如果之前在Map中存放了对应Key的弱引用,则取出这个弱引用中的EngineResource,否则返回null。

可以看出这里实现了一个缓存,由于它是运行时才有这个Map的,所以它是一个 内存缓存,它的存放的是弱引用的图片资源,所以在内存回收时是会被立即回收的。
回到 loadFromActiveResources()中:

// Engine.java
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
...
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}

// EngineResource.java
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;
}

如果返回的 active不为null,则调用 ​​active.acquire()​​​,它的作用是给计数器 acquired自增1(在主线程和非回收状态下),这里并不知道为什么要这么做。所以就回去了。这样​​loadFromActiveResources()​​就分析完了,往下走:

//Engine.java  #load
...
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
...

从 ​​loadFromCache()​​​中获取EngineResource,如果拿得到就调用 ​​onResourceRead()​​返回。来看下这个方法:

// Engine.java
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}

EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}

这个方法和之前的 ​​loadFromActiveResources​​​差别不大,先看看 ​​getEngineResourceFromCache(key)​​:

// Engine.java
private final MemoryCache cache;

private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key); // 1

final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
}
return result;
}

这个方法就是从 ​​MemoryCache​​中拿出一个EngineResource并返回。那我们要去看看 MemoryCache做了啥,但是MemoryCache是一个接口类,没有实现,在向前找的时候发现它是在创建 Glide initalizeGlide() 的过程中创建的,这里直接看创建的代码:

// GlideBuilder.java
@NonNull
Glide build(@NonNull Context context) {
....
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
....
}

// LruResourceCache.java
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
...
}

​LruResourceCache​​​实现了MemoryCache,并继承了LruCache。
这里简单的描述一下LruCache,它是最近最少使用策略,实现原理是 accessOrder为true的 LinkedHashMap。所以在​​getEngineResourceFromCache​​​的注释1中,它就是调用了 ​​LinkedHashMap.remove(key)​​来看Map中是否有key对应的 EngineResources。

回到 loadFromCache中:

// Engine.java  #loadFromCache
...
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
....

如果remove到的资源是不为null的,则调用 acquire()方法,并且调用 ​​activeResources.activate(key, cached)​​,我们看看这个方法做了什么:

// ActiveResources.java
void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
getReferenceQueue(),
isActiveResourceRetentionAllowed);

ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}

它把 key和EngineResource作为元素 put到了 ActiveResources的 HashMap中。便于以后 ​​loadFromActiveResources​​​去拿。
然后最后return为null。 ​​​loadFromCache()​​就结束了。可以看出来 LruCache也是在运行时产生的,所以它也是内存缓存

这里出现了一个问题,这里出现了两个内存缓存,一个是HashMap缓存,一个是LruResourcesCache的缓存,在前者拿资源拿不到的情况下,去拿后者,如果后者拿到了,会把该资源放到前者中缓存。乍一看是没事找事,为什么不把所有的资源都放在一个cache下存储呢?这需要往后面的代码看。

接下来继续load()函数中:

private final Jobs jobs;
// Engine.java #load
...
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
....

通过 Jobs去拿一个 ​​EngineJob​​​,如果 EngineJob不为null,则调用其 ​​addCallback()​​​,这个方法最终也会调用 ​​onResourceReady()​​​,并返回一个 ​​LoadStatus​​ 。先来看看 jobs的get方法:

// Jobs.java
private final Map<Key, EngineJob<?>> jobs = new HashMap<>();
private final Map<Key, EngineJob<?>> onlyCacheJobs = new HashMap<>();

EngineJob<?> get(Key key, boolean onlyRetrieveFromCache) {
return getJobMap(onlyRetrieveFromCache).get(key);
}

private Map<Key, EngineJob<?>> getJobMap(boolean onlyRetrieveFromCache) {
return onlyRetrieveFromCache ? onlyCacheJobs : jobs;
}

​onlyRetrieveFromCache​​这个字段中文意思为:是否只从Cache中搜索。默认情况下是 false。它就会去 jobs这个HashMap中拿EngineJob。这里也是一个缓存,但他并不是之间缓存图片资源。

EngineJob它不是一个图片资源,那它是什么呢?这里还不是很清晰,先往load下面走,

// Engine.java  #load
...
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);

DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);

jobs.put(key, engineJob);

engineJob.addCallback(cb);
engineJob.start(decodeJob);
...

这里是load方法的最后一步,在上述三种缓存都命中不到资源的情况下,会创建一个 ​​EngineJob​​​和一个 ​​DecodeJob​​​,将 key和engineJob一起绑定加入到 Jobs的HashMap,这样之后上面的jobs缓存就有资源可以找,然后调用 ​​EngineJob.addCallback​​​和 ​​Engine.start()​​ 这里的重点是 start方法,它是没有内存缓存后,去做的事情,所以这里算是一个新的开始。 也就是说,load可以看成两个部分,这里开始就是第二个部分。

1.2 EngineJob的start方法解析

// EngineJob.java
private final GlideExecutor diskCacheExecutor;
private final GlideExecutor sourceExecutor;
private final GlideExecutor sourceUnlimitedExecutor;
private final GlideExecutor animationExecutor;

public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}

EngineJob里面有线程池执行器 ​​GlideExecutor​​​,它就是一个​​ExecutorService​​​,因为加载图片是一个耗时操作,所以放到子线程做,所以这里出现了 Executor,其次,在EngineJob中,exeutor执行器并不是只有一个:
它会先根据缓存策略,拿到对应executor,比如这里的 :

// EngineJob.java 
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();

就会根据 “是否在Cache中解码” 这个条件来拿 disCacheExecutor,还是其他三个中的执行器。
在默认情况下, DiskCacheStategey是 AUTOMATIC的,这里就是true,即用的是 diskCacheExecutor。
接下来就会拿着这个线程执行器去执行 ​​​DecodeJob​​​,就是调用它的 ​​run()​​:

// DecodeJob.java
@Override
public void run() {
....
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (Throwable t) {
....
} finally {
...
}
}

notifyFailed()是通知失败的方法,然后没取消,就调用 ​​runWrapped()​

// DecodeJob.java
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}

这里根据 runReason分成了三种情况:

  • INITIALIZE
    在第一次我们提交执行任务的状态
  • SWITCH_TO_SOURCE_SERVICE
    当我们想要从 DiskCache 的做法切换到 Source执行的做法(这里我也不知道干嘛的)
  • DECODE_DATA
    该状态标识着:当我们在子线程中拿到了一个我们从未拥有过的资源,这个时候我们需要切换回主线程,去让主线程得到这个资源。

当然了,上面的三个是执行 DecodeJob的原因,它强调的是原因,DecodeJob又维护了一套当前的状态,和上面产生了联系:

  • INITIALIZE
    和上面初始时一样
  • RESOURCE_CACHE
    根据一个 缓存资源 进行 解码(Decode)
  • DATA_CACHE
    根据一个 缓存的源数据 进行 解码(Decode)
  • SOURCE
    根据 获取到的源数据 进行 解码
  • ENCODE
    在一个 资源成功加载后, 为了将它缓存起来,进行编码
  • FINISHED
    要结束了

假设我们是第一次进入到这个方法,那我们的runReason是 INITIALIZE,接下来会调用 ​​getNextState(Stage.INITIALIZE)​​:

// DecodeJob.java
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}

// 这里是默认情况下的 DiskCacheStaratey(磁盘)的缓存模式:
// DiskCacheStrategy。java
public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return dataSource == DataSource.REMOTE;
}

@Override
public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
EncodeStrategy encodeStrategy) {
return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
|| dataSource == DataSource.LOCAL)
&& encodeStrategy == EncodeStrategy.TRANSFORMED;
}

@Override
public boolean decodeCachedResource() {
return true;
}

@Override
public boolean decodeCachedData() {
return true;
}
};

在 INITIALIZE状态下调用了 ​​getNextStage()​​​,state就变成了 ​​Stage.RESOURCE_CACHE​​​,接下来在 runWrapped中:
调用了 ​​​currentGenerator = getNextGenerator()​​:

private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}

返回了一个 ​​ResourceCacheGenerator​​​,它现在是做什么的还不知道。
继续往下走 ,会调用 ​​​runGenerators()​​:

// DecodeJob.java
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) { // 1
stage = getNextStage(stage); // 2
currentGenerator = getNextGenerator(); // 3

if (stage == Stage.SOURCE) {
reschedule(); // 4
return;
}
}
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}

这个方法会开启一个 while循环,判断条件中第三个较为关键:调用 ​​currentGenerator.startNext()​​​ 如果这个方法的结果为false,则 stage进入到下一个状态, currentGenerator换成下一个执行器。
在当前状态为 ​​Stage.SOURCE​​时,调用了 ​​reschedule()​​,然后return,也就是说 SOURCE为当前decodeJob执行的最后一个状态。
如果要一直到最后 SOURCE,那么当前的DecodeJob会走这么个顺序:
(1)DecodeJob的状态:
RESOURCE_CACHE -> DATA_CACHE -> SOURCE
(2)currentGenrator的变化:
​ResourceCacheGenerator​​ -> ​​DataCacheGenerator​​ -> ​​SourceGenerator​​ (3) startNext的执行
​ResourceCacheGenerator.startNext() = false​​ -> ​​DataCacheGenerator .startNext() = false​

这就是在 ​​startNext()​​一直为false的情况下。 我们现在就去研究一下这个方法,由于 Generator是一个发生器,在这里分成了两个,所以startNext()有必要拆出来讲解。

startNext()这个方法有点长,并且借助了一个新的类 ​​DecodeHelper​​的辅助,这个类主要做了组件注册,用来扩展或替换Glide的默认加载,还有负责解码、编码的Registry类。而且我们其实还并不是很清楚 ResourceCacheGenerator、DataCacheGenerator、SourceGenerator这些所关联 DataFetcher的类是做什么的,所以有必要先简单的介绍他们。

1.3 DataFetcher、ModelLoader以及DecodeHelper的介绍

1.关于DataFetcher
它是一个接口,先来看看它的定义:

// DataFetcher.java
public interface DataFetcher<T> {
interface DataCallback<T> {
void onDataReady(@Nullable T data); // 资源已经弄好了
void onLoadFailed(@NonNull Exception e); //加载失败回调
}
void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback); //加载方法
void cleanup(); //资源释放
void cancel(); // 取消加载
@NonNull
Class<T> getDataClass();
@NonNull
DataSource getDataSource();
}

​DataFetcher​​​是Glide的数据加载模块,定义了数据的加载时遵循的框架。
它的使用大概就是:通过调用 ​​​loadData()​​​来加载资源,当加载成功后,调用 ​​onDataReady()​​​进行回调。如果失败,调用 ​​onLoadFailed()​​​。非常好懂。
它有很多个实现类,比如:

  • LocalUriFetcher
    使用 ContentResolver 从本地资源的Uri加载数据
  • AssetPathFetcher
    使用 AssetManager从asset path中获取数据的抽象类
  • HttpUrlFetcher
    具体加载的数据类型为 InputStream,从网络Url中获取数据

2.关于ModelLoader
它也是个接口,来看看代码:

public interface ModelLoader<Model, Data> {
class LoadData<Data> {
public final Key sourceKey;
public final List<Key> alternateKeys;
public final DataFetcher<Data> fetcher;

public LoadData(@NonNull Key sourceKey, @NonNull DataFetcher<Data> fetcher) {
this(sourceKey, Collections.<Key>emptyList(), fetcher);
}

public LoadData(@NonNull Key sourceKey, @NonNull List<Key> alternateKeys,
@NonNull DataFetcher<Data> fetcher) {
this.sourceKey = Preconditions.checkNotNull(sourceKey);
this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
this.fetcher = Preconditions.checkNotNull(fetcher);
}
}
@Nullable
LoadData<Data> buildLoadData(@NonNull Model model, int width, int height,
@NonNull Options options);
boolean handles(@NonNull Model model);
}

它是一个工厂类的接口,用于将任意复杂的数据模型转换为具体的数据类型。
复杂的数据模型:通过给Glide设置了的各种属性,比如动画、缩放、模糊等等配置
具体的数据类型:​​​LoadData​​ ---- 由一系列的Key和DataFetcher组成的数据结构

它可以构建像 HttpGlideUrlLoader,内部构建一个HttpUrlFetcher,来进行网络的下载资源
也可以构建 FileLoader,内部构建一个 FileFetcher,来进行文件资源的获取。

它和DataFetcher是一起绑定的。

3. DecodeHelper
具体讲解需要费大量时间,所以可能会对本文出现岔道,所以具体请看这篇 ​​​DecodeHelper类相关方法分析​​​ 我们在 ​​Glide.with().load(url)​​ 的load中输入了获取图片资源的方式,这个方式可能是String、BitMap、Uri等。而 ​​DecodeHelper​​的作用就是通过分析这个方式,分析出我们想要获取图片资源,需要哪些 ModelLoader、CacheKeys等。

比如当我们输入的是一个Url的字符串,它就能分析出我们所需的 cacheKey为:[GlideUrl,ObjectKey],从而获取到对应的LoadData

1.4 DataFetcherGenerator.startNext()

在1.2节中讲到,如果说一直要执行到 SOURCE状态,要先执行 ​​ResourceCacheGenerator.startNext()​​​和 ​​DataCacheGenerator .startNext() = false​​ ,这里看下两个方法的实现。

1.关于ResourceCacheGenerator的实现

// ResourceCacheGenerator.java
public boolean startNext() {
List<Key> sourceIds = helper.getCacheKeys(); // 1
if (sourceIds.isEmpty()) { // 2
return false;
}
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses(); // 3
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
}
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
if (resourceClassIndex >= resourceClasses.size()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
resourceClassIndex = 0;
}

Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = helper.getTransformation(resourceClass);
currentKey =
new ResourceCacheKey(
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions()); // 4
cacheFile = helper.getDiskCache().get(currentKey); // 5
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}

return started;
}

有了 1.3节中的介绍,我们可以来解析上面的源码:
注释1:根据model(即我们在Glide中传入的图片资源获取方式),可以拿到 cacheKeys,当我们传入的是Stirng类型的(比如Url),那么这个 cacheKeys就有两个元素 [GlideUrl,ObjectKey]
注释2:如果拿到的cacheKeys为空,则直接返回false
注释3:获取resourceClass的信息,就是glide所支持的资源类的信息,比如Bitmap
注释4:进行循环,根据注释1中的 Key和注释3中的 资源类信息,生成一个缓存的 key对象 ---- ​​​ResourceCacheKey​​​,
注释5: 拿着这个key去 DiskCache中寻找是否有缓存对象。如果查找成功,获取 它的 ​​​ModelLoader​​对象,失败则继续循环。

这个while循环就是为了找到对应的 ModelLoader信息,如果找不到则返回false,如果找到了则进入下一个while循环,这个循环中就是根据上一个循环找到的ModelLoader数组,遍历每个 ModelLoader,然后执行其 fetcher的 ​​loadData()​​方法进行资源的加载。然后返回true。

​ResourceCacheGenerator.startNext()​​这个方法总结为一句话就是: 根据ResourceCacheKey从磁盘缓存中获取缓存
如果获取不到,则执行​​​DataCacheGenerator .startNext()​

2.关于DataCacheGenerator的实现

// DataCacheGenerator.java
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}

Key sourceId = cacheKeys.get(sourceIdIndex);
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}

loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData =
modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}

这个方法和上述的 ResourceCacheGenerator中的startNext()很像,其实很多部分都是一样的, 但是这里有唯一一处不同是,就是去DiskCache中寻找用缓存用的Key并不是 ​​ResourceCacheKey​​​,而是一个 ​​DataCacheKey​​。

他们的区别是:

  • ResourceCacheKey
    指向的是包含缩减采样/转换资源数据的缓存文件
  • DataCacheKey
    指向的是包含原始未修改源数据的缓存文件

这也就能看出​​DataFetcherGenerator​​的作用是:

  • ResourceCacheGenerator
    DataFetcherGenerator实现类,从包含缩减采样/转换资源数据的缓存文件生成DataFetchers
  • DataCacheGenerator
    DataFetcherGenerator实现类,从包含原始未修改源数据的缓存文件生成DataFetchers

在1.2节中,这两个方法执行完之后,stage来到了 ​​SWITCH_TO_SOURCE_SERVICE​​​状态,那么他会runReason为调用 ​​reschedule()​​​,拿着非diskCacheExecutor的线程执行器再去执行一遍任务。
也就是重新执行了 ​​​DecodeJob.runWrapped()​​​,而这个方法最终为调用 ​​SourceGenerator.startNext()​

3.关于SourceGenerator的实现

// SourceGenerator.java
@Override
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data); // 1
}

if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;

loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this); // 2
}
}
return started;
}

// 将数据缓存到Disk中
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
helper.getDiskCache().put(originalKey, writer);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished encoding source to cache"
+ ", key: " + originalKey
+ ", data: " + dataToCache
+ ", encoder: " + encoder
+ ", duration: " + LogTime.getElapsedMillis(startTime));
}
} finally {
loadData.fetcher.cleanup();
}

sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}

注释1:通过 ​​cacheData()​​​将数据缓存到 DataDiskCache中,dataToCache是在数据加载好,这个对象才有的,所以在一开始进来,这个方法是不会执行的。
注释2:通过 ​​​DataFetcher.loadData()​​​请求数据。
具体的请求这里就不细讲了。只知道在请求成功,会调用 ​​​SourceGenerator.onDataReady()​​表示数据已经获取成功:

// SourceGenerator.java
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
cb.reschedule(); // 1
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}

注释1:如果数据可以被缓存,则调用 ​​reschedule()​​​,这个方法会在 ​​SourceGenerator.startNext()​​​中执行 ​​cacheData()​​​将数据和新的​​DataCacheKey​​一起保存在了磁盘中。最后返回true。

最终还是调用了 ​​onDataFetcherReady()​​​使得代码回到了 DecodeJob 中。
而DecodeJob就会对数据解码,和重新编码后缓存,调用到 ​​​EngineJob.onResourceReady()​​​中
最后 EnginJob会向主线程发送消息,最终告知 ​​​ViewTarget()​​执行成功并显示对应图片资源。

1.5 DecodeJob.run时序图

从上面的源码,我这边总结了一下 run的时序图,便于理解方法的调用:

Glide load源码、缓存机制分析_java


到这里 load方法算是分析的七七八八了。

上述分析中缓存讲的并不是很清楚,所以这里需要再补充一些缓存的知识。

2. Glide缓存总结

我们知道 Glide的缓存机制,都在 ​​load()​​​流程中。
一路看下来,我们知道了它总共有两种缓存,一种是 内存缓存,一种是 磁盘缓存:

2.1 内存缓存

内存缓存出现在 load的前半段:

// Engine.java
...
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
...
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
...

他们的作用:

  • 第一层内存缓存: loadFromActiveResources
    从​​​ActiveResources​​所维护的HashMap中获取弱引用缓存资源
  • 第二层内存缓存:loadFromCache
    LruCache中获取缓存资源,LruCache就是一个拓展的 LinkedHashMap
    如果可以获取成功, 把获取到的数据放到 ActiveResources 中。

这里的问题是,同样是内存缓存,为什么要分成两层?为什么第二层在获取成功后把数据移动到第一层?
我们需要注意的是在我们获取成功的时候,代码调用了: ​​​active.acquire()​​​ 资源有一个计数器,当被引用时,自增1。
这个地方就解释了为什么内存缓存有两层:
将内存缓存的资源分成 正在使用的 和 没有使用的,正在使用的用弱引用缓存,这样能优化运行时内存性能,在需要时随时回收,当前不用的图片则通过LruCache来缓存。这样也能节省空间

这里就解决了两层内存缓存的疑惑。
这里还有另外一个问题,那就是 资源是什么时候被放到 内存Cache里面去的呢?

在 1.5节中,我们看到了回调,在 ​​Engine.onEngineJobComplete()​​中:

// Engine.java
public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
Util.assertMainThread();
if (resource != null) {
resource.setResourceListener(key, this);

if (resource.isCacheable()) {
activeResources.activate(key, resource); // 1
}
}

jobs.removeIfCurrent(key, engineJob);
}

注释1:这个方法就是将资源放到了 ​​ActiveResources​​中。

那只有这个地方,LruCache中的缓存在哪里存的呢?这就要看Resource资源了,我们在EngineResource中,看到它除了​​acquire()​​进行引用自增,它同样还有一个方法,如果减少一个引用,他就会自减:

// EngineResource.java
void release() {
...
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}

如果发现引用为0,说明没有地方引用这个图片资源了,就会调用 ​​Engine.onResourceReleased()​​:

// Engine.java
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
...
if (resource.isCacheable()) {
cache.put(cacheKey, resource); // 1
} else {
resourceRecycler.recycle(resource); // 2
}
}

注释1:如果资源可以缓存,则缓存在 LruCache中
注释2:如果不能缓存,则直接释放资源。

这样一来,就解答了开始的疑问。这里做一个总结:

  1. 当资源被成功加载后,在 Engine的​​onEngineJobComplete()​​回调中,放到内存的 ActiveResources所维护的 弱引用HashMap中。
  2. 当一个资源所持有的引用为0,则调用​​Engine.onResourceReleased​​,如果这个资源可以被缓存,则它加入到 内存的 LruCache中。如果不能被缓存则直接释放。

2.2 磁盘缓存

磁盘缓存是在 DecodeJob的run方法中,通过 ​​ResourceCacheGenerator.startNext()​​​和 ​​DataCacheGenerator.startNext()​​做的:

  1. ResourceCacheGenerator.startNext()
    读取硬盘中可以匹配​​​ResourceCacheKey​​的文件,它是包含缩减采样/转换资源数据的缓存文件
  2. DataCacheGenerator.startNext()
    读取硬盘中可以匹配​​​DataCacheKey​​的文件,它是包含原始未修改源数据的缓存文件
    在资源加载成功后,在​​​SourceGenerator.cacheData()​​中写入到磁盘中。

它会通过下面的代码来更新:

// ResourceCacheKey.java
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
byte[] dimensions = arrayPool.getExact(8, byte[].class);
ByteBuffer.wrap(dimensions).putInt(width).putInt(height).array();
signature.updateDiskCacheKey(messageDigest);
sourceKey.updateDiskCacheKey(messageDigest);
messageDigest.update(dimensions);
if (transformation != null) {
transformation.updateDiskCacheKey(messageDigest);
}
options.updateDiskCacheKey(messageDigest);
messageDigest.update(getResourceClassBytes());
arrayPool.put(dimensions);
}

至于调用时机,请看这个步骤:

// 在我们获取磁盘缓存时调用的方法
helper.getDiskCache().get(currentKey);

而这个 ​​DiskCache()​​​是 ​​DiskLruCacheWrapper​​​类型,
在 DecodeJob解析完数据后,会调用 ​​​notifyEncodeAndRelease()​​,示意对数据进行自己的编码然后缓存,这个方法如下:

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
...
stage = Stage.ENCODE;
try {
if (deferredEncodeManager.hasResourceToEncode()) {
deferredEncodeManager.encode(diskCacheProvider, options);
}
}...
}

这里调用了 ​​DeferredEncodeManager.encode()​​:

void encode(DiskCacheProvider diskCacheProvider, Options options) {
...
try {
diskCacheProvider.getDiskCache().put(key,
new DataCacheWriter<>(encoder, toEncode, options));
} ...
}

这里则把数据给put到了 ​​DiskLruCacheWrapper​​中。这就完成了磁盘缓存。

这里有一个问题,为什么磁盘缓存也需要两层,这个非常容易理解:

  • 通过DataCacheGenerator缓存
    它是在数据被加载完后put进去的,它是原始的未改变的图片资源,是最原生的。
  • 通过ResourceCacheGenerator缓存
    它是在数据被加载完后,经过重新编码,再缓存到磁盘中的。经过重新编码,图片资源会减小采样或者更换资源形式,这样可以用更少的空间来替代原生的。在获取的时候也先从该缓存中获取,是轻一级的磁盘缓存。

因为磁盘读取是耗时操作,所以磁盘缓存是在 子线程(GlideExecutor)中做的。和快速的内存缓存分开了~


举报

相关推荐

0 条评论