Glide的加载流程
执行加载主流程
接上一文,昨天讲到图片加载,最终调用到了onSizeReady的方法,调用了其中的engine.load方法
@Override
public synchronized void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
...
status = Status.RUNNING;
//计算缩略图的尺寸
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
if (IS_VERBOSE_LOGGABLE) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
//加载流程
loadStatus =
engine.load(
glideContext,
model,//对应rul,图片地址
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),//默认是Object.class
transcodeClass,//默认是Drawable.class
priority,
requestOptions.getDiskCacheStrategy(),//缓存策略,默认是AUTOMATIC
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
}
我们再直接跟进Engin.load方法
public synchronized <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
//1创建资源索引key,内存缓存的唯一键值
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
//2首先从内存中找当前正在显示的资源缓存
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;
}
//3没有找到,就从内存缓存资源中加载图片
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;
}
//4还是没找到,如果这个任务已经在队列中,获取已经存在的加载任务
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
//5还没有找到,如果是新建加载任务,创建EngineJob和DecodeJob,然后开始任务
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
//6新建解码任务,真正执行数据加载和解码的类
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
//7缓存加载任务
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
//开启解码任务
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
这个方法代码有点长,却是Glide缓存机制的核心,先说一下几个名词:
活动资源Active Resources 正在显示的资源
内存缓存Memory cache 显示过的资源
资源类型Resources 被解码、转换后的资源
数据来源Data 源文件(未处理过的资源)
可以看到,这其实就是磁盘+内存+网络三级缓存!
再来分析一下上面load方法的执行流程:
- 创建资源索引key,这个是唯一的,可以看到,生成一个key需要资源本身、图片宽高转换类型、加载参数等,只要这些不一致,就不是同一张图片,所以即便是显示的图片宽高不一样,Glide都会重新执行一次加载过程,而不是使用内存中加载已有的图片资源。
- 2和3的流程:如果要加载的图片已经正在显示,直接使用已有的资源,如果图片没有在显示,但正好在内存缓存中,没有销毁,那么可以直接使用缓存中的资源。
- 4到8流程:如果内存中没有可以直接使用的图片资源,那么就要开始从网络或者本地磁盘中去加载一张图片
所以我们可以直接到engineJob.start()方法
//start方法就是根据diskCacheStrategy策略获取一个executor来执行DecodeJob
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
//这里根据缓存策略,决定使用哪一个Executor,默认情况返回DiskCacheExecutor
//共有三种执行器,diskcacheExecutor,sourceExecutor,sourceUnlimitedExecutor
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
可以看到,在start方法直接启用了线程池的execute方法,可以知道,DecodeJob类一定是个runnable,我们去看它的run方法。
//DecodeJob.java
@Override
public void run() {
...
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
//重点在这
runWrapped();
} catch (CallbackException e) {
...
}
}
如果一切正常,那么会进入runWrapped方法
private void runWrapped() {
switch (runReason) {
//默认状态为INITISLIZE
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);
}
}
默认的状态是INITIALIZE,那我们来看它调用的几个方法
//获取任务执行阶段:初始化,读取转换后的缓存,读取原文件(未处理)缓存,远程图片加载,结束状态
private Stage getNextStage(Stage current) {
switch (current) {
//初始状态
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
//状态2,读取转换后的缓存
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
//状态3,读取原文件缓存
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
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);
}
}
//这里开始加载执行
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
//这里Generator.startNext方法中就是加载过程,如果成功就返回true并跳出循环,否则切换Generator继续执行
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();
}
}
@Override
public void reschedule() {
//更改执行目标为:SOURCE服务。当然也只有在stage == Stage.SOURCE的情况下会被调用。
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);//这里callback正是EngineJob。
}
//代码跟进EngineJob类中,可以看到实现方法。
@Override
public void reschedule(DecodeJob<?> job) {
// 可以看到,这里获取的SourceExecutor来执行decodeJob。
//也就巧妙地将此decodeJob任务从cacheExecutor切换到了SourceExecutor,这样分工协作更加高效。
getActiveSourceExecutor().execute(job);
}
可以看到,这里几个方法构成了Glide的解码流程:尝试从转换过的本地资源加载图片;尝试从没有处理过的原始资源中加载图片;尝试远程加载图片。通过状态的切换来寻找下一个加载器,直到加载到这张图,返回成功,如果找不到,返回失败。
再捋一捋:
- 这一段是从最开始没有命中内存缓存开始,然后执行Engine的start方法,默认情况下会获取到cacheExecutor执行器来执行decodeJob任务;
- decodeJob这个runnable的run方法就会被调用;
- 因为RunReason为INITIALIZE,接着获取stage,默认返回Stage.RESOURCE_CACHE
- 这时通过getNextGenerator就返回了ResourceCacheGenerator加载器
- 接着调用ResourceCacheGenerator的startNext方法,从转换后的缓存中读取已缓存的资源
- 如果命中,就结束任务并回调结果,反之,切换到DataCacheGenerator,同样的,如果还没命中就切换到SourceGenerator加载器(比如初次加载,没有任何缓存,就会走到这)。
- 切换到SourceGenerator环境,等它结束后,结束任务,回调结果,流程结束。
网络相关——加载网络图片
从上面我们可以知道,SourceGenerator可以加载网络与本地图片,那么我们直接看SourceGenerator调用startNext方法
/**
* SourceGenerator
* DataFetcher的简介:Fetcher的意思是抓取,所以该类可以称为数据抓取器
* 作用就是根据不同的数据来源(本地,网络,Asset等)
* 以及读取方式(Stream,ByteBuffer等)来提取并解码数据资源,实现类如下
* AssetPathFetcher:加载Asset数据
* HttpUrlFetcher:加载网络数据
* LocalUriFetcher:加载本地数据
* 其他实现类...
*
*/
@Override
public boolean startNext() {
//忽略缓存部分逻辑
...
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
//是否有更多的ModelLoader
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.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
继续跟进代码,private volatile ModelLoader.LoadData<?> loadData,是一个接口,就需要我们来找出它的实现类了
回到Glide的初始化中
Glide(...) {
...
registry
...
.append(String.class, InputStream.class, new StringLoader.StreamFactory())
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
....
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
...
.register(GifDrawable.class, byte[].class, gifDrawableBytesTranscoder);
ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
glideContext =
new GlideContext(
context,
arrayPool,
registry,
imageViewTargetFactory,
defaultRequestOptions,
defaultTransitionOptions,
defaultRequestListeners,
engine,
isLoggingRequestOriginsEnabled,
logLevel);
}
这构造方法是很长的,我们只看我们关心的网络相关的部分,可以看到,我们将String.class Uri.class GlideUri.class三种类型注入了不同的Factory,这个Factory就是用来创建ModelLoader的,ModelLoader就是用来加载图片的。
public class Registry {
//各种功能类注册器。加载、转换、解码、加密等。
private final ModelLoaderRegistry modelLoaderRegistry;
private final EncoderRegistry encoderRegistry;
private final ResourceDecoderRegistry decoderRegistry;
private final ResourceEncoderRegistry resourceEncoderRegistry;
private final DataRewinderRegistry dataRewinderRegistry;
private final TranscoderRegistry transcoderRegistry;
private final ImageHeaderParserRegistry imageHeaderParserRegistry;
...
//modelLoader注册
public <Model, Data> Registry append(Class<Model> modelClass, Class<Data> dataClass,
ModelLoaderFactory<Model, Data> factory) {
modelLoaderRegistry.append(modelClass, dataClass, factory);
return this;
}
...
}
//继续跟进代码,ModelLoaderRegistry类中
public synchronized <Model, Data> void append(Class<Model> modelClass, Class<Data> dataClass,
ModelLoaderFactory<Model, Data> factory) {
multiModelLoaderFactory.append(modelClass, dataClass, factory);
cache.clear();
}
//最后进入MultiModelLoaderFactory类中的add方法
private <Model, Data> void add(Class<Model> modelClass, Class<Data> dataClass,
ModelLoaderFactory<Model, Data> factory, boolean append) {
Entry<Model, Data> entry = new Entry<>(modelClass, dataClass, factory);
//entries是一个list。所以,到这里就知道注册的LoaderFactory被缓存到了列表中,以便后面取用。
entries.add(append ? entries.size() : 0, entry);
}
通过以上代码,知道了ModelLoaderFactory在Glide初始化时注册到了一个列表中,以后使用。在分析DecodeJob的代码里时,我们使用SourceGenerator加载远程图片,并分析到了loadData.fetcher.loadData(helper.getPriority(), this);
是真正加载数据的地方。
我们先看loadData在哪赋值的
loadData = helper.getLoadData().get(loadDataListIndex++);
可以看到,是使用Helper来调用方法。
//DecodeHelper
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
//根据model类型,通过Glide对应的registry获取ModelLoader列表
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = modelLoaders.size(); i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
LoadData<?> current =
modelLoader.buildLoadData(model, width, height, options);
//循环创建出LoadData,用户后面加载
if (current != null) {
loadData.add(current);
}
}
}
return loadData;
}
可以看到,这其实是一个列表,可以知道,在
.append(String.class, InputStream.class, new StringLoader.StreamFactory()
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
这三个Factory中,因为我们load的是一个String,所以我们肯定是StringLoader中
public class StringLoader<Data> implements ModelLoader<String, Data> {
private final ModelLoader<Uri, Data> uriLoader;
// Public API.
@SuppressWarnings("WeakerAccess")
public StringLoader(ModelLoader<Uri, Data> uriLoader) {
this.uriLoader = uriLoader;
}
@Override
public LoadData<Data> buildLoadData(@NonNull String model, int width, int height,
@NonNull Options options) {
Uri uri = parseUri(model);
if (uri == null || !uriLoader.handles(uri)) {
return null;
}
return uriLoader.buildLoadData(uri, width, height, options);
}
...
/**
* Factory for loading {@link InputStream}s from Strings.
*/
public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {
@NonNull
@Override
public ModelLoader<String, InputStream> build(
@NonNull MultiModelLoaderFactory multiFactory) {
//关键在这儿
return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
}
@Override
public void teardown() {
// Do nothing.
}
}
}
可以看到,其实它在里面MultiModelLoaderFactory通过Uri.class和InputStream.class创建一个ModelLoader给StringLoader,所以StringLoader的加载功能转移了。而且根据注册关系知道转移到了HttpUriLoader中。而它对应的Fetcher就是HttpUrlFetcher。
那么可以直接看HttpUrlFetcher的load代码
/**
* HttpUrlFetcher
* HttpUrlFetcher的简介:网络数据抓取器,通俗的来讲就是去服务器上下载图片,支持地址重定向(最多5次)
*
*/
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
} catch (IOException e) {
...
}
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
//重定向次数过多
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
//通过URL的equals方法来比较会导致网络IO开销,一般会有问题
//可以参考http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
//下面开始,终于看到了可爱的HttpUrlConnection下载图片
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that
// redirects will be handled by recursive calls to this method, loadDataWithRedirects.
urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
stream = urlConnection.getInputStream();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (isHttpOk(statusCode)) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (isHttpRedirect(statusCode)) {
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
// Closing the stream specifically is required to avoid leaking ResponseBodys in addition
// to disconnecting the url connection below. See #2352.
cleanup();
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == INVALID_STATUS_CODE) {
throw new HttpException(statusCode);
} else {
throw new HttpException(urlConnection.getResponseMessage(), statusCode);
}
}
看到这,终于找到了网络通讯的代码,就是通过HttpUrlConnection来获取数据流并返回。当然也可以自定义使用OkHttp。可以参考Glide4.8集成现有OkHttpClient并加载https图片blog.csdn.net/ysy950803/a…
总结
- 从最开始没有命中内存缓存开始,然后执行Engine的start方法,默认情况下会获取到cacheExecutor执行器来执行decodeJob任务;
- decodeJob这个runnable的run方法就会被调用;
- 因为RunReason为INITIALIZE,接着获取stage,默认返回Stage.RESOURCE_CACHE
- 这时通过getNextGenerator就返回了ResourceCacheGenerator加载器
- 接着调用ResourceCacheGenerator的startNext方法,从转换后的缓存中读取已缓存的资源
- 如果命中,就结束任务并回调结果,反之,切换到DataCacheGenerator,同样的,如果还没命中就切换到SourceGenerator加载器(比如初次加载,没有任何缓存,就会走到这)。
- 切换到SourceGenerator环境,如果是网络请求图片,就调用HttpUrlFetcher的LoadData方法
- LoadData通过调用loadDataWithRedirects,里面默认使用HttpUrlConnection来下载图片,等它结束后,结束任务,回调结果,流程结束。
分析源码的时候,我们只应该关注主流程,不要太过于纠结每一个细节。Glide的源码非常值得仔细反复阅读,每次都能学到不少东西,当然也是一个考验耐心的事情。
参考
-
Glide最全解析系列文章 - 郭神基于3.x版本的。
下面是我的公众号,欢迎大家关注我