我们知道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的时序图,便于理解方法的调用:
到这里 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:如果不能缓存,则直接释放资源。
这样一来,就解答了开始的疑问。这里做一个总结:
- 当资源被成功加载后,在 Engine的
onEngineJobComplete()
回调中,放到内存的 ActiveResources所维护的 弱引用HashMap中。 - 当一个资源所持有的引用为0,则调用
Engine.onResourceReleased
,如果这个资源可以被缓存,则它加入到 内存的 LruCache中。如果不能被缓存则直接释放。
2.2 磁盘缓存
磁盘缓存是在 DecodeJob的run方法中,通过 ResourceCacheGenerator.startNext()
和 DataCacheGenerator.startNext()
做的:
- ResourceCacheGenerator.startNext()
读取硬盘中可以匹配ResourceCacheKey
的文件,它是包含缩减采样/转换资源数据的缓存文件 - 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)中做的。和快速的内存缓存分开了~