磁盘缓存策略
Glide的磁盘缓存策略定义在DiskCacheStrategy中,这是一个抽象类,定义了4个抽象方法,用来判断不同场景下的缓存策略:
//是否缓存原始数据
public abstract boolean isDataCacheable(DataSource dataSource);
//是否缓存已经转换过的数据
public abstract boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);
//是否解码缓存的已转换过的数据
public abstract boolean decodeCachedResource();
//是否解码缓存的原始数据
public abstract boolean decodeCachedData();
目前定义了5种策略:
- ALL:缓存网络请求的原始数据,且缓存已转换的数据
- NONE:不缓存原始数据和转换的数据
- DATA:缓存从本地读取的原始资源数据,和网络请求的原始数据,不缓存转换的数据
- RESOURCE:不缓存原始数据,缓存转换的数据
- AUTOMATIC:缓存网络加载的原始数据,根据数据来源和编码策略决定是否缓存转换的数据
数据来源
DataSource定义了5种数据来源:
public enum DataSource {
LOCAL,//本地数据,从assets,file等途径加载
REMOTE,//网络请求
DATA_DISK_CACHE,//磁盘缓存的原始数据
RESOURCE_DISK_CACHE,//磁盘缓存的转换过的数据
MEMORY_CACHE,//内存缓存
}
具体的加载逻辑
磁盘缓存策略决定了磁盘缓存的加载逻辑,主要的加载过程在DecodeJob中定义,上文已说明过,大致的流程是:
磁盘缓存策略能缓存转换过的数据:
- 先读取转换过的缓存,如果找到则返回
- 如果没有找到,那么会试图去读取原始数据的磁盘缓存,如果找到则跳到步骤6
- 如果原始数据的缓存也没有,那么会进行网络请求
- 请求完成后,会保存原始数据到文件缓存中(如果磁盘缓存策略允许缓存原始数据)
- 然后通过原始数据的缓存处理器加载原始数据或者直接进行第6步
- 将原始数据解码
- 将原始数据进行转换
- 将转换的数据进行缓存
其中的流程与具体的判断细节有关,可能会发生变化。
磁盘缓存策略只缓存原始数据:
流程与上述相似,从步骤2开始,只是第8步不会将转换过的数据缓存到本地。
不缓存转换的数据,也不缓存原始数据:
流程从第3步开始,第8步不会将转换过的数据缓存到本地。
从上面的流程看,请求的原始数据都会保存到本地,使得整个流程能运转起来,因为除了DiskCacheStrategy.NONE,其他的几种策略都是需要将网络请求的原始数据缓存的。
ResourceCacheGenerator
转换的缓存数据生成器
实现了DataFetcherGenerator接口:
interface DataFetcherGenerator {
//结果回调
interface FetcherReadyCallback {
//让Glide重新调度
void reschedule();
//数据加载完成
void onDataFetcherReady(
Key sourceKey,
@Nullable Object data,
DataFetcher<?> fetcher,
DataSource dataSource,
Key attemptedKey);
//数据加载失败
void onDataFetcherFailed(
Key attemptedKey, Exception e, DataFetcher<?> fetcher, DataSource dataSource);
}
//启动一个DataFetcher去加载数据,true表明启动,false未启动
boolean startNext();
//取消数据获取
void cancel();
}
所以主要关注ResourceCacheGenerator的startNext()和cancel()方法
public boolean startNext() {
//helper为DecodeHelper,由DecodeJob进行创建和初始化,保存了本次图片加载的基本参数:图片类型,宽,高,磁盘缓存策略,需要做的转换Transformation等
List<Key> sourceIds = helper.getCacheKeys();
//判断本次加载的资源类型是否注册了数据加载器
if (sourceIds.isEmpty()) {
return false;
}
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
//不能加载资源类型
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
throw new IllegalStateException(
"Failed to find any load path from "
+ helper.getModelClass()
+ " to "
+ helper.getTranscodeClass());
}
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);
//生成一个ResourceCacheKey,看该类中的equals()和hashCode()方法,可以看到影响这个key的因素是宽,高,转换,解码器,sourceKey,signature和options。
currentKey =
new ResourceCacheKey(
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
//获取转换后的数据的缓存文件
cacheFile = helper.getDiskCache().get(currentKey);
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,用于加载数据
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);
}
}
//true说明有转换后的数据的缓存文件,并且已开始加载;false表明未找到缓存
return started;
}
startNext()方法主要是寻找转换后的数据的缓存文件,并且加载它。
public void cancel() {
LoadData<?> local = loadData;
if (local != null) {
local.fetcher.cancel();
}
}
cancel()主要调用了DataFetcher的cance()方法,暂停加载
ResourceCacheGenerator也实现了DataFetcher.DataCallback接口,用于处理缓存加载的回调。
@Override
public void onDataReady(Object data) {
cb.onDataFetcherReady(
sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey);
}
@Override
public void onLoadFailed(@NonNull Exception e) {
cb.onDataFetcherFailed(currentKey, e, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE);
}
缓存的加载结果,会调用FetcherReadyCallback的回调方法,而cb是DecodeJob实现并传入的,因此结果都交给DecodeJob处理。
DecodeJob具体的处理逻辑最后再分析。
DataCacheGenerator
原始数据的缓存加载器
DataCacheGenerator实现了相同的接口,DataFetcherGenerator和DataFetcher.DataCallback,直接看具体的实现方法:
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
//cacheKeys由DecodeHelper提供,包含了该资源类型的加载方法
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
//生成一个DataCacheKey,由equals和hashCode方法可知,影响因素是sourceKey和signature
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
//获取原始数据缓存文件
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
//当前的资源类型是File,所以需要文件加载器
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
//该类型的数据加载器,用来加载缓存文件,由于类型是File,因此用的是FileLoader
loadData =
modelLoader.buildLoadData(
cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
//因为类型是File,用的是FileFetcher,加载文件,返回数据
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
startNext()主要是获取缓存文件,并进行加载。
cancel()调用了DataFetcher.cancel()方法,用来取消加载。
实现的onDataReady和onLoadFailed回调与ResourceCacheGenerator一样,都是交给DecodeJob处理。
SourceGenerator
资源加载器。
实现的接口与上述两个XXGenerator一样。
public boolean startNext() {
//如果磁盘缓存策略允许缓存原始数据,那么加载数据后,会先缓存到文件中,再读取并处理
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
//只有cacheData()后,sourceCacheGenerator才非空,且这个sourceCacheGenerator的类型是DataCacheGenerator,只是跟DecodeJob中创建的不一样,此处只是利用了DataCacheGenerator重新加载一遍原始数据,并把结果回调给DecodeJob(因为sourceCacheGenerator中传入的回调是SourceGenerator,最后都会传给DecodeJob)
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//按顺序获取该资源类型的LoadDada,并进行加载数据
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
startNextLoad(loadData);
}
}
return started;
}
startNextLoad()方法用于执行具体的加载逻辑,调用LoadData的DataFetcher去加载数据:
private void startNextLoad(final LoadData<?> toStart) {
loadData.fetcher.loadData(
helper.getPriority(),
new DataCallback<Object>() {
@Override
public void onDataReady(@Nullable Object data) {
//判断是否同一个请求
if (isCurrentRequest(toStart)) {
onDataReadyInternal(toStart, data);
}
}
@Override
public void onLoadFailed(@NonNull Exception e) {
if (isCurrentRequest(toStart)) {
onLoadFailedInternal(toStart, e);
}
}
});
}
onDataReadyInternal()处理加载结果:
void onDataReadyInternal(LoadData<?> loadData, Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
//如果磁盘策略允许缓存原始数据,那么保存到dataToCache变量中
dataToCache = data;
//DecodeJob实现了reschedule方法,最终还是会调用SourceGenerator的startNext()方法,进行文件缓存,并读取的操作
cb.reschedule();
} else {
//如果不缓存原始数据,则直接把结果回调给DecodeJob
cb.onDataFetcherReady(
loadData.sourceKey,
data,
loadData.fetcher,
loadData.fetcher.getDataSource(),
originalKey);
}
}
DecodeJob中处理逻辑下篇再分析。