携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
基本用法
Glide最基本的用法如下:
Glide.with(context)
.load(url)
.into(imageView)
本文就从这三个方法去学习Glide的源码。
PS:本文基于4.13.2版本的Glide源码。
废话不多说直接开始!
继续into
前文再续,书接上一回。本来打算一个方法出一篇,三篇就完结了,没料到这个into如此多弯弯绕绕,只得再出一篇。
上回说到EngineJob.start,我们就继续看看这个方法做了什么吧!
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor =
decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
executor.execute(decodeJob);
}
可以看到就是获取了一个Executor然后执行了decodeJob。既然DecodeJob是可以被execute的,那它必然是实现了Runnable接口的。那就看看DecodeJob的run方法里面是什么东东。
public void run() {
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (CallbackException e) {
throw e;
} catch (Throwable t) {
//一些出错的处理,省略。
} finally {
if (localFetcher != null) {localFetcher.cleanup();}
}
}
声明了一个DataFetcher,执行runWrapped,最后cleanup这个DataFetcher。
顾名思义这个DataFetcher是用来获取数据的,而具体的操作就在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);
}
}
runWrappered方法根据当前RunReason而执行不同的行为。RunReason则是一个枚举类,表明了当前执行的这次DecodeJob需要干什么。有三个值:
- INITIALIZE:表明是第一次执行。
- SWITCH_TO_SOURCE_SERVICE:表明需要从源获取数据。
- DECODE_DATA:表明Glide从一个不属于Glide的线程中获取了一些数据,需要切换回Glide的线程去处理这些数据。
而RunReason的状态流转以及DecodeJob的工作还与另一个枚举类息息相关:Stage。该枚举类的官方注释为:
Where we're trying to decode data from.
与其说Stage表明Glide将从什么地方decode数据,我更愿意将Stage理解成任务执行的阶段。Stage有六个值:
- INITIALIZE:初始化阶段。
- RESOURCE_CACHE:从缓存的Resource中decode。
- DATA_CACHE:从缓存的源数据decode。
- SOURCE:从获取的源中decode。
- ENCODE:成功加载后对转换后的资源进行编码。
- FINISHED:结束阶段。
只有中间的RESOURCE_CACHE、DATA_CACHE、SOURCE这三个值是表明从什么地方decode数据的。而其他的值却是在描述任务执行的阶段。而Stage的状态流转基本上是在getNextStage方法中的,我们来看看这个方法。
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);
}
}
可以看到,Stage会根据缓存策略的不同而被指定为RESOURCE_CACHE、DATA_CACHE、SOURCE的其中一个。同时也让Stage只能从上往下流转,而不能反向流转。而事实上,从RESOURCE_CACHE到DATA_CACHE再到SOURCE,获取数据的源头是越来越远的。
这里解释一下Glide中,Resource、Data、Source的区别:
- Source:即是源头,获取图片数据的源头,可以理解为文件(本地)或者url(网络)。
- Data:也称Source Data,即是源数据,是指从Source获取到的图片原数据(未经编解码)。
- Resource:资源,即是已经转码完成的图片,基本上可以直接进行展示(已编码)。
其中Data和Resource都会被缓存下来(前提是开启了对应的缓存选项)。
回到getNextStage中,我们假设是第一次执行getNextStage(也即对应runWrappered方法中RunReason为INITIALIZE的情况)会有以下流程:
graph TD
A[input] --> |INITIALIZE| B[getNextStage]
B --> C{开启Resource缓存?}
C --> |true| D[RESOURCE_CACHE]
D --> E[return]
C --> |false| F{开启Data缓存?}
F --> |true| G[DATA_CACHE]
G --> E
F --> |false| H[SOURCE]
H --> E
好了,由于考虑开启缓存的流程过于长,本例只考虑禁用缓存的情况,也即Stage为SOURCE的情况。
回过头来看runWrappered,这里再贴一遍:
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
//先只看INITIALIZE情况,其他先忽略。
}
}
stage是SOURCE,接着获取Generator,说明一下,这个Generator是DataFetcher的生成器。看方法名也能猜出个大概,获取了Generator就执行这个Generator。既然是DataFetcher的生成器,那运行内容自然就是生成DataFetcher了。并且没有执行DataFetcher的代码,大胆猜测一下DataFetcher是自动运行的。先来看看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);
}
}
很简单,根据不同的Stage生成不同的Generator。本例的Stage为SOURCE,自然返回的是SourceGenerator。
接下来就是运行这个SourceGenerator了。
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled
&& currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}
首先while循环的判断条件里面有一个startNext,我们就来看看SourceGenerator的startNext。
public boolean startNext() {
//①
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
boolean isDataInCache = cacheData(data);
if (!isDataInCache) {
return true;
}
}
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;
startNextLoad(loadData);
}
}
return started;
}
- ①处代码。与缓存逻辑有关,这里暂时忽略。
- ②处代码。循环获取与执行LoadData。如果有符合条件的LoadData则开始执行,并且started的值将会设为true。相反,如果没有找到符合条件的LoadData,started则将会是false。started的结果会一直传递到DecodeJob的runGenerator中循环的判断条件里。如果返回的结果为false(证明该Generator并不能获取到资源),这时候DecodeJob就会寻找下一个Stage(也即上文讲到的“从近到远”)和与之对应的Generator,并再次尝试获取。
LoadData是什么东西呢?它是ModelLoader的内部类。那ModelLoader又是什么东西呢?
我们回想一下,之前我们传入的网址,Glide保存起来的时候的变量名是什么?不正是model吗?此model正是ModelLoader中的model。ModelLoader本身是一个接口,里面有两个泛型Model和Data,Model是传入的类型,Data是期望获取到的源数据类型,每一个实现负责将某个特定的Model类型加载出某个特定Data类型的数据。
为什么变量model的类型是Object?,就是因为要支持传入各种类型的Model,比如URL、String、ResourceId等等。而ModelLoader的功能则是从Model中加载出资源。举个例子,假如传入的model是ResourceId,也即Int类型,则我们可以假设存在一个IntModelLoader(当然这是假的,实际上人家叫ResourceLoader),它就负责从ResourceId加载资源。
事实上,Glide实例初始化的时候,就已经生成了好多张注册表,有ResourceDecoder、ResourceEncoder等的注册表,自然也有ModelLoader的注册表。Model注册表的类名为ModelLoaderRegistry,代码就不贴了,大概说一下吧。ModelLoaderRegistry内部实际上是一个List,Entry是一个包含一个Model类型、一个Data类型以及与Model、Data相匹配的ModelLoader的Factory的类。需要说明的是,即使是Model以及Data的类型都相同,也不代表就指定了某个特定的factory。比如Model为String,Data为InputStream可以是StringLoader(负责处理ContentResolver类的获取),也可以是DataUriLoader(负责处理Base64的图片)。
看回上面startNext的代码,循环地从DecodeHelper中获取LoadData。而LoadData的生成依赖于ModelLoader,所以要获取到LoadData就先要获取到ModelLoader。DecodeHelper中存放着GlideContext,GlideContext则存放着所有注册表。而从注册表检索ModelLoader只需要传入model,注册表会根据传入的model类型取出所有Model为该类型的ModelLoader(可能细心的同学会发现,刚刚上面说了,注册表里面存放的是factory,怎么检索出来的是ModelLoader呢?其实就是注册表在检索的过程中,一并将ModelLoader从factory中build出来了)。这时候还没能直接返回给SourceGenerator,还需要进行一个筛选的过程。比如说同样Model是String,既可能是ContentResolver的格式,也可能是Base64的内容。这时候就需要“问一问”各个ModelLoader是否有能力处理当前传入的这个model。ModelLoader有一个handles的方法,传入model,能处理的返回true,不能处理返回false。通过handles的筛选,剩下的ModelLoader都能处理传入的model。获取到了ModelLoader,就可以使用buildLoadData来构建对应的LoadData了。至此我们就得到了LoadData了。
检索LoadData的过程还是有点复杂的,以图的形式来总结一下。
sequenceDiagram
DecodeHelper ->> Registry:getLoadData()
Registry ->> ModelLoaderRegistry:getModelLoaders()
ModelLoaderRegistry ->> Factory:getModelLoadersForClass()
Factory ->> Factory:build()
Factory ->> ModelLoaderRegistry:List<ModelLoader>
ModelLoaderRegistry ->> Registry:Filtered List<ModelLoader>
Registry ->> DecodeHelper:Filtered List<ModelLoader>
DecodeHelper ->> ModelLoader:buildLoadData
ModelLoader ->> DecodeHelper:List<LoadData>
既然已经获取到了符合条件的LoadData,那就开始执行它吧!
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);
}
}
});
}
LoadData内部有一个对应的DataFetcher,loadData方法即是实际获取数据的方法了。上面我们猜测,DataFetch是自动运行的,看来也是对的。回调的DataCallback很简单,一个获取成功一个获取失败。我们看看获取成功,检查了一下是否为当前的请求(重复请求忽略旧请求),然后调用onDataReadyInternal并将获取到的数据传入。
void onDataReadyInternal(LoadData<?> loadData, Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
cb.reschedule();
} else {
//①
cb.onDataFetcherReady(
loadData.sourceKey,
data,
loadData.fetcher,
loadData.fetcher.getDataSource(),
originalKey);
}
}
重点关注①处代码,把获取到的数据回调给DecodeJob。顺便提一嘴,网络请求的DataFetcher为HttpUrlFetcher,请求的底层是用HttpURLConnection。有兴趣的读者自己去了解吧!
public void onDataFetcherReady(
Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey;
this.currentData = data;
this.currentFetcher = fetcher;
this.currentDataSource = dataSource;
this.currentAttemptingKey = attemptedKey;
this.isLoadingFromAlternateCacheKey = sourceKey != decodeHelper.getCacheKeys().get(0);
if (Thread.currentThread() != currentThread) {
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
try {
//①
decodeFromRetrievedData();
} finally {
GlideTrace.endSection();
}
}
}
将回调过来的数据都赋一下值,然后关注①处代码。从这里到实际解码数据的调用链如下:
graph LR
decodeFromRetrievedData --> decodeFromData --> decodeFromFetcher
接下来我们看看decodeFromFetcher。
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
return runLoadPath(data, dataSource, path);
}
这个方法做了两件事:获取LoadPath;执行这个LoadPath。
LoadPath这个东西其实跟LoadData很相似。LoadData负责从Source中读取Data,而LoadPath则负责将Data解码成所需的Resource。和LoadData一样,LoadPath也是通过注册表检索的。检索的过程如下:
graph LR
获取Decoder以及Transcoder--> 构建DecodePath列表 --> 构建LoadPath
LoadPath包含一个DecodePath的列表;DecodePath则包含一个Decoder以及一个Transcoder。注册表会首先找出所有符合条件的Decoder,然后再根据Decoder找出所有符合条件的Transcoder,接着将每一个Decoder和每一个Transcode都一一配队组合起来构建出一个DecodePath的列表,最后封装成LoadPath返回给DecodeJob。
本例中,Decoder负责将获取到的Data数据Decode成Bitmap,Transcoder负责将Bitmap转换成BitmapDrawable。
runLoadPath接下来的调用链如下:
graph LR
DecodeJob.runLoadPath --> LoadPath.load --> LoadPath.loadWithExceptionList --> DecodePath.decode
DecodePath.decode分成三步:
- decode:将data解码成Resource。本例中将InputStream解码成Bitmap。
- transform:对解码后的Resource进行变换(例如切圆角等),回调给DecodeJob执行。
- transcode:对变换后的Resource进行转码。本例中将Bitmap转码成BitmapDrawable。
至此我们已经得到了最终的Resource了。回过头看回decodeFromRetrieveData方法。
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);
throwables.add(e);
}
if (resource != null) {
//②
notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
} else {
runGenerators();
}
}
- ①处代码即是上述解码过程。
- ②处代码则是获取到最终的Resource,开始回调结果。
回调的顺序如下:
graph LR
DecodeJob.notifyEncodeAndRelease --> DecodeJob.notifyComplete --> EngineJob.onResourceReady --> Engine.onEngineJobComplete
EngineJob.onResourceReady --> SingleRequest.onResourceReady
Engine.onEngineJobComplete回调是通知Engine移除该Job的。
SingleRequest.onResourceReady是通知Resource获取成功以及传递Resource的。
继续传递:
graph LR
SingleRequest.onResourceReady --> ImageViewTarget.onResourceReady --> DrawableImageViewTarget.setResource
最后把资源回调到了DrawableImageViewTarget的setResource中。
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
view自然就是into传入的ImageView。
终于完成了!
结言
终于完成了对Glide的流程源码分析了。不得不说Glide真的是非常非常复杂。现在只是进行最简单的流程,也这么长。甚至还不惜跳过了缓存相关的东西,个人认为Glide的缓存实现是非常典型且具有教育意义的,但只能是以后有机会再继续这个Glide系列了。说实话深入研究Glide的话,势必还有很多东西需要探究,比如占位图实现、错误图实现、各种图片变换处理、不同的Data类型和不同的Resource类型的处理……任重道远啊!