您的位置:首页 > 移动开发 > Android开发

Glide源码分析(六)——从DecodeJob相关实现看图片加载流程

2017-03-23 19:19 501 查看
DecodeJob是集数据加载、解析和回调通知于一体的包装内,它在内部根据配置的不同情况将相关的工作委托给不同的类来实现,是Glide内实现数据加载功能的中枢。首先来看其中会用到的几个比较重要的类。

LocalPath和DecodePath

DecodePath内部保存有两个主要的成员,一个是由ResourceDecoder组成的解码器集合,用来从指定的数据源中解码数据;另一个是ResourceTranscoder转换器,用来将一种类型的Resource(例如文件IO流类型的Resource、ByteBuffer类型的bitmap数据Resource等)转换成另外一种(一般是drawable、二进制类型的bitmap和gif)更加直接的Resource。

在使用DecodePath进行数据加载的时候,先遍历尝试使用ResourceDecoder集合来进行数据解码,加载完成后再使用transcoder对资源进行转换,以便后面更加利于使用。

但是要注意的是,DecodePath并不负责寻找数据源(数据源实际上是来自缓存DiskCache或者DataFetcher的流中),它需要调用者指定数据源,然后它只做剩余的工作:使用ResourceDecoder将数据解码成一个类型,然后使用ResourceTranscoder将数据转换成最终需要的类型。

LocalPath在DecodePath外面再做了一层包装,它有一个DecodePath列表,在外部调用其数据加载接口的时候,它并不直接进行数据加载,而是遍历内部的DecodePath集合,将加载的需求委派给DecodePath,只要其中一个加载成功就完成数据的加载。

LocalPath的DecodePath列表是通过Registery来确定的,相关的方法如下:

/**
* 获取能够将dataClass数据解码成resourceClass类型,并最后将数据转换成transcodeClass类型的LoadPath
* @param dataClass
* @param resourceClass
* @param transcodeClass
* @return
*/
public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
Class<Data> dataClass, Class<TResource> resourceClass, Class<Transcode> transcodeClass) {
LoadPath<Data, TResource, Transcode> result =
loadPathCache.get(dataClass, resourceClass, transcodeClass);
if (result == null && !loadPathCache.contains(dataClass, resourceClass, transcodeClass)) {
List<DecodePath<Data, TResource, Transcode>> decodePaths =
getDecodePaths(dataClass, resourceClass, transcodeClass);
// It's possible there is no way to decode or transcode to the desired types from a given
// data class.
if (decodePaths.isEmpty()) {
result = null;
} else {
result = new LoadPath<>(dataClass, resourceClass, transcodeClass, decodePaths,
exceptionListPool);           // 构造LoadPath
}
loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
}
return result;
}

/**
* 获取能够将dataClass数据解码成resourceClass类型,并最后将数据转换成transcodeClass类型的DecodePath集合
* @param dataClass
* @param resourceClass
* @param transcodeClass
* @return
*/
private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths(
Class<Data> dataClass, Class<TResource> resourceClass, Class<Transcode> transcodeClass) {

List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>();
// 通过查询注册的ResourceDecoder,得到他们能够加载dataClass并转换成的具体的资源类型(registerResourceClass可能是resourceClass的子类)
List<Class<TResource>> registeredResourceClasses =
decoderRegistry.getResourceClasses(dataClass, resourceClass);

for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
// 通过查询得到能够进一步从registerReourceClass通过transcoder转换成的具体资源类型(registeredTrancodeClass可能是transcodeClass的子类)
List<Class<Transcode>> registeredTranscodeClasses =
transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);

for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {

List<ResourceDecoder<Data, TResource>> decoders =
decoderRegistry.getDecoders(dataClass, registeredResourceClass);      // 得到具体的一个decoder
ResourceTranscoder<TResource, Transcode> transcoder =     // 得到transcoder
transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
decodePaths.add(new DecodePath<>(dataClass, registeredResourceClass,      // 使用DecodePath包装decoder和相应的transcoder
registeredTranscodeClass, decoders, transcoder, exceptionListPool));
}
}
return decodePaths;
}


可以看到LoadPath需要依赖于DecodePath列表,而DecodePath列表最终也是根据dataClass和resourceClass来联合查找ResourceDecoder,然后再根据resourceClass和transcodeClass来联合查找ResourceTranscoder构成的。

DecodeHelper

DecodeHelper是DecodeJob内的一个辅助类,它结合GlideContext、Registry等类来确定当前DecodeJob进行数据加载、解析所需要何种ModelLoader、LoadPath、ResourceDecoder等。它为相应的DecodeJob封装实现了那些需要在Registry和GlideContext中进行查询的功能接口。

DataFetcherGenerator

这个类主要用来进行数据加载,接口实现非常简单,就是两个控制数据加载流程的接口和一个内部定义的加载结果回调接口。它主要有三个子类:ResourceCacheGenerator、DataCacheGenerator和SourceGenerator,三个子类的实现都类似,下面看DataCacheGenerator实现的startNext方法:

public boolean startNext() {        // 寻找下一个可以load的任务
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}

Key sourceId = cacheKeys.get(sourceIdIndex);
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {        // 命中缓存文件
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);     // 找到能够处理file类型的modelLoader
modelLoaderIndex = 0; // 重置modelLoaderIndex,以便可以尝试所有的modelLoader
}
// 如果在这里得到的modelLoaders为空,表示这个generator不能找到这个cacheFile对应的Loader,
// 也就是无法进行数据load。因此直接跳过。

}

// 找到一个可以进行load的任务
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {        // 从loaders中找到一个能够加载该缓存文件类型的loader
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData =
modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
helper.getOptions());
// 由于helper中已经固定了resouceClass和transcodeClass,因此能够通过hasLoadPath进行判断是否有LoadPath进行后续的转换
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {  // 可以进行数据加载
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);    // 开始进行数据加载
}
}
return started;
}


原理很简单,因为要从缓存中获取数据,因此先根据key查找缓存文件,然后找到可以从缓存文件中进行数据加载的laoder列表,最后从这些loader中找到一个可以处理这种加载的loader就可以由loader内部的fetcher进行数据加载了。

DecodeJob数据加载流程分析

DecodeJob的执行入口是run函数,run函数的核心调用函数runWrapped函数如下:

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

/**
* 能够看出数据源的优先顺序
* @param current
* @return
*/
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
// 缓存策略是否允许从cacheResource中获取数据,如果不允许,这跳过RESOURCE_CACHE阶段
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 Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}


这里罗列了两个内部包含case语句的函数,runWrapped中的runReason case相当于是DecodeJob的状态机实现,而getNextStage中的stage case则是数据源选择的优先级实现,这是二者实现的区别。runWrapped中调用的runGenerators函数会根据数据源的不同来生成不同的generator,以便能够从对应的数据源中正确地加载数据。使用generator进行数据加载的函数实现如下:

/**
* 实现数据加载
*/
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
// 一直找到一个有效的generator进行数据加载为止
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {  // 使用startNext进行数据加载

// 到这里说明上次首选的generator数据加载失败,尝试使用下一个数据源的generator进行数据加载
stage = getNextStage(stage);
currentGenerator = getNextGenerator();

if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
// We've run out of stages and generators, give up.
// 没有找到一个有效的generator,因此无法加载数据也就无法进行decode工作,故通知失败!
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}

// Otherwise a generator started a new load and we expect to be called back in
// onDataFetcherReady.
}


该函数首先会使用首选的generator进行数据加载,当首先的generator加载失败后就尝试使用另一个数据源的generator进行加载,直达加载成功发起成功或者没有找到合适的generator为止,如果加载成功,当数据ready后,就会回调DecodeJob中的onDataFetcherReady方法,否则使用reschedule回调通知外部调用者表明该DecodeJob数据加载失败。

如果一切数据加载正常,那么onDataFetcherReady回调会调用到decodeFromRetrievedData函数,这个函数实现如下:

private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Retrieved data", startFetchTime,
"data: " + currentData
+ ", cache key: " + currentSourceKey
+ ", fetcher: " + currentFetcher);
}
Resource<R> resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
exceptions.add(e);
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();       // 说明当前generator获取的数据失败了,因此尝试使用下一个generator来获取数据
}
}


这个函数就会调用其他函数尝试对DataFetcher加载成功的数据进行后续的解码和转换处理了。假设一切正常,最终会调用到decodeFromFetcher函数,其相关实现如下:

@SuppressWarnings("unchecked")
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
// 根据加载成功的数据类型,并结合decodeJob需要解码和转换的类型构造一个loadPath
LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
return runLoadPath(data, dataSource, path);
}

private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource,
LoadPath<Data, ResourceType, R> path) throws GlideException {
// 根据数据源构造一个rewinder,以便后面在decode和transcode中使用到
DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);       // 获取一个能够处理data类型的rewinder
try {
return path.load(rewinder, options, width, height,
new DecodeCallback<ResourceType>(dataSource));      // 对数据的解码和转换工作委托实现在这里了!
} finally {
rewinder.cleanup();
}
}


以上实现在注释中已经说明,首先构造一个LoadPath,然后使用LoadPath进行数据解码和转换,LoadPath解码和转换的原理在上面已经说过了,这里不再赘述,只是要注意的是,在转码过程中还注入了一个转换的干预实现DecodeCallback,这个内部类对LoadPath中ResourceDecoder解码得到的数据根据情况进行了处理,然后再将这个数据交给LoadPath中的ResourceTranscoder进行转换。最后DecodeJob将经过LoadPath解码和转换的数据返回,整个加载、解码和转换流程就结束了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐