Glide源码分析-具体请求

基本概念

上一部分我们分析了Glide的生命周期管理,到这一步时RequestManager已经构建完成了,接下来就该依照外界传入的配置构建具体Request发出加载请求了,也就是终于到了真正干活的地方了。Glide的优秀之处(也是为什么有那么多代码)的原因就是在这个部分扩展性很强,你甚至可以把Glide完全改造,包括具体的网络请求、请求的数据类型、对最终数据的操作都可以重写,只让Glide处理线程调度和成名周期绑定,而不只是加载图片和gif。

请求流程图

具体请求类图

相关类的介绍

  • RequestManager:用于接受外界传入的uri根据数据类型生成GenericRequestBuilder
  • GenericRequestBuilder:用于构建具体用于数据请求的Request
  • Request:携带一个具体请求相关的所有信息
  • Engine:当Request被执行时,有Engine负责管理总体的数据请求流程
  • EngineRunnable: 实现了Runnable,有这个类持有EngineJobDecodeJob在子线程进行原始数据请求
  • EngineJob: 负责开启数据请求及将结果通过EngineJobListenerResourceCallback回调给EngineJob和具体的Request
  • DecodeJob: 真正执行数据请求的类,向外界暴露从缓存中请求和从数据源请求原始数据的方法

源码分析

与之前一样,这个过程只看只看类的作用只能看个大概,还是需要搬出来源码解释问题。我们以Glide.with(context).load(url).into(imageview)为例,首先看RequestManager中的相关方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string);
}

public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}

private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
//构建传入的数据类型对应的ModelLoader
ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
Glide.buildFileDescriptorModelLoader(modelClass, context);
if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
+ " which there is a registered ModelLoader, if you are using a custom model, you must first call"
+ " Glide#register with a ModelLoaderFactory for your custom model class");
}

return optionsApplier.apply(
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}

这部分源码比较抽象,但是做的事情并不复杂,就是拿到传入的数据后,根据数据类型构建一个ModelLoader,这个ModelLoader的作用后面讲。然后再拿着这个ModelLoader构建一个DrawableTypeRequest,这个DrawableTypeRequestGenericRequestBuilder的子类,然后看它的相关源码:

1
2
3
4
5
6

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
this.model = model;
isModelSet = true;
return this;
}

尴尬,好像是一个建造者模式,只是传入了数据,并没有进行操作,但是也可以知道,Glide.with(context).load(url).into(imageview)这个完整请求的最后一步,into()方法肯定也在这个类里面,那我们继续看相关源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}

if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
return into(glide.buildImageViewTarget(view, transcodeClass));
}

public <Y extends Target<TranscodeType>> Y into(Y target) {
Util.assertMainThread();
if (target == null) {
throw new IllegalArgumentException("You must pass in a non null Target");
}
if (!isModelSet) {
throw new IllegalArgumentException("You must first set a model (try #load())");
}

Request previous = target.getRequest();

if (previous != null) {
previous.clear();
requestTracker.removeRequest(previous);
previous.recycle();
}

Request request = buildRequest(target);
target.setRequest(request);
lifecycle.addListener(target);
requestTracker.runRequest(request);
return target;
}

首先是into(imageview)这个方法,无视无用信息,关键代码只有最后一句,执行的是into(target)方法。然后我们继续看这个方法,这时候很明显了,在into(target)执行时,会构建一个Request,然后被执行runRequest。再看runRequest方法的源码:

1
2
3
4
5
6
7
8
9

public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}

很简单,本质上就是执行Requestbegin()方法。Request是一个接口,我们看它的主要实现类GenericRequest的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

@Override
public void begin() {
startTime = LogTime.getLogTime();
if (model == null) {
onException(null);
return;
}

status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}

if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}

@Override
public void onSizeReady(int width, int height) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;

width = Math.round(sizeMultiplier * width);
height = Math.round(sizeMultiplier * height);

ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);

if (dataFetcher == null) {
onException(new Exception("Failed to load model: \'" + model + "\'"));
return;
}
ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
loadedFromMemoryCache = true;
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);
loadedFromMemoryCache = resource != null;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}

这部分代码如果想要完全搞清楚就必须翻源码了,基本流程是begin()方法被执行之后,会等待onSizeReady的回调执行,这句不用看也知道意思是要等View被绘制出来拿到具体尺寸之后,才真正发起请求,因为Glide在加载图片的时候都会根据View的尺寸对源数据进行压缩,缓存也只会缓存这种数据(任何图片库都一样,毕竟内存吃紧)。在onSizeReady毁掉中,我们只看关键代码,其实就是Engineload()方法,我们再看这部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb)
{

Util.assertMainThread();
long startTime = LogTime.getLogTime();

final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
//从内存缓存中取数据
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}

//从activeResources中取数据
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}

//内存缓存没有命中,发起一个EngineRunnable,请求源数据
EngineJob current = jobs.get(key);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}

EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(runnable);

if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}

这个方法比较长,但是只看方法名的话也完全可以理解,先从内存缓存中取数据,没有命中就从activeResources中取,还没有命中就发起一个EngineRunnable请求源数据,EngineRunnable会持有一个EngineJob和一个DecodeJob,我们依次来看它们的作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

EngineJob.java

private final ExecutorService diskCacheService;
private final ExecutorService sourceService;

public void start(EngineRunnable engineRunnable) {
this.engineRunnable = engineRunnable;
future = diskCacheService.submit(engineRunnable);
}

@Override
public void submitForSource(EngineRunnable runnable) {
future = sourceService.submit(runnable);
}

EngineRunnable.java

private final EngineRunnableManager manager;

private void onLoadFailed(Exception e) {
if (isDecodingFromCache()) {
stage = Stage.SOURCE;
manager.submitForSource(this);
} else {
manager.onException(e);
}
}

@Override
public void run() {
if (isCancelled) {
return;
}

Exception exception = null;
Resource<?> resource = null;
try {
resource = decode();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Exception decoding", e);
}
exception = e;
}

if (isCancelled) {
if (resource != null) {
resource.recycle();
}
return;
}

if (resource == null) {
onLoadFailed(exception);
} else {
onLoadComplete(resource);
}
}

贴代码的时候才发现这两部分必须一起分析···先看EngineJob,它内部有两个ExecutorService,很明显一个是用于请求磁盘数据,另一个用于请求源数据。EngineJob实现了EngineRunnableManager这个接口,它只是用来管理这两个线程池的相关操作,同时通过持有的ResourceCallbackEngineJobListener将结果回调给RequestEngine,线程具体的run()方法在我们上面贴出的EngineRunnable里面,然后我们再看这部分代码,本质上就是通过decode()方法进行数据请求,如果属于请求磁盘数据且失败了,那么就去请求源数据,最终将结果回调给EngineJob。那么关键就是这个decode()方法了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

private Resource<?> decode() throws Exception {
if (isDecodingFromCache()) {
return decodeFromCache();
} else {
return decodeFromSource();
}
}

private Resource<?> decodeFromCache() throws Exception {
Resource<?> result = null;
try {
result = decodeJob.decodeResultFromCache();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Exception decoding result from cache: " + e);
}
}

if (result == null) {
result = decodeJob.decodeSourceFromCache();
}
return result;
}

private Resource<?> decodeFromSource() throws Exception {
return decodeJob.decodeFromSource();
}

这部分代码只看方法名也很清晰,就是通过持有的DecodeJob分别从缓存(磁盘缓存)和数据源去取数据,然后就引出了真正干活的DecodeJob了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123

//因为Glide磁盘缓存的策略中,可以选择缓存根据View尺寸压缩过后的文件。因此取缓存的第一步就是根据尺寸去取对应的缓存
public Resource<Z> decodeResultFromCache() throws Exception {
if (!diskCacheStrategy.cacheResult()) {
return null;
}

long startTime = LogTime.getLogTime();
Resource<T> transformed = loadFromCache(resultKey);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded transformed from cache", startTime);
}
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transcoded transformed from cache", startTime);
}
return result;
}

//根据缓存策略当ResultCache没有命中时就从磁盘缓存中取源数据
public Resource<Z> decodeSourceFromCache() throws Exception {
if (!diskCacheStrategy.cacheSource()) {
return null;
}

long startTime = LogTime.getLogTime();
Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded source from cache", startTime);
}
return transformEncodeAndTranscode(decoded);
}


//根据key获取磁盘缓存的具体代码
private Resource<T> loadFromCache(Key key) throws IOException {
File cacheFile = diskCacheProvider.getDiskCache().get(key);
if (cacheFile == null) {
return null;
}

Resource<T> result = null;
try {
result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
} finally {
if (result == null) {
diskCacheProvider.getDiskCache().delete(key);
}
}
return result;
}

//取缓存失败,就去取源数据(从网络或是从本地文件路径)
public Resource<Z> decodeFromSource() throws Exception {
Resource<T> decoded = decodeSource();
return transformEncodeAndTranscode(decoded);
}

//取源数据的具体过程
private Resource<T> decodeSource() throws Exception {
Resource<T> decoded = null;
try {
long startTime = LogTime.getLogTime();
final A data = fetcher.loadData(priority);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Fetched data", startTime);
}
if (isCancelled) {
return null;
}
decoded = decodeFromSourceData(data);
} finally {
fetcher.cleanup();
}
return decoded;
}

//对取到的源数据进行解码操作
private Resource<T> decodeFromSourceData(A data) throws IOException {
final Resource<T> decoded;
if (diskCacheStrategy.cacheSource()) {
decoded = cacheAndDecodeSourceData(data);
} else {
long startTime = LogTime.getLogTime();
decoded = loadProvider.getSourceDecoder().decode(data, width, height);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded from source", startTime);
}
}
return decoded;
}

//对取到的数据进行transform转换,写入磁盘缓存以及转码操作,获取最终target需要的数据
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
long startTime = LogTime.getLogTime();
Resource<T> transformed = transform(decoded);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transformed resource from source", startTime);
}

writeTransformedToCache(transformed);

startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transcoded transformed from source", startTime);
}
return result;
}

//写缓存
private void writeTransformedToCache(Resource<T> transformed) {
if (transformed == null || !diskCacheStrategy.cacheResult()) {
return;
}
long startTime = LogTime.getLogTime();
SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
diskCacheProvider.getDiskCache().put(resultKey, writer);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Wrote transformed from source to cache", startTime);
}
}

这部分代码比较多,但是也基本就是取源数据的全部了。那么以一个网络请求数据源为例,这部分代码在哪里呢?就在decodeSource()里的final A data = fetcher.loadData(priority);这一句了,这个DataFetcher的所有实现类,就是从所有路径取请求源数据的实现了,我们以OkHttp对应的OkHttpSteamFetcher为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

@Override
public InputStream loadData(Priority priority) throws Exception {
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());

for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();

Response response;
call = client.newCall(request);
response = call.execute();
responseBody = response.body();
if (!response.isSuccessful()) {
throw new IOException("Request failed with code: " + response.code());
}

long contentLength = responseBody.contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
return stream;
}

用过OkHttp的同学都知道,这是OkHttp最基本的一个请求,通过这个请求,我们也通过传入的路径拿到了我们需要的数据流。到这时候,一个完整的请求就基本结束了。

不过还没完,因为数据拿到了,也处理完了,还没显示在View上,这时候就要回头看Request拿到的请求结果的回调里做了什么了,还是以实现类GenericRequest为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

private void onResourceReady(Resource<?> resource, R result) {
// We must call isFirstReadyResource before setting status.
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;

if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
isFirstResource)) {
GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
target.onResourceReady(result, animation);
}

notifyLoadSuccess();

if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
+ (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
}
}

与我们预期的一致,请求结果被传递给Request对应的targetonResourceReady方法,在这个方法里完成了最终数据与View的绑定工作。到这时候,一个完整的图片加载过程就完成了。

总结

当然,我们这一篇的分析真是跟着源码把数据请求的过程走了一遍,其中还有许多有意思的设计并没有设计。比如在Target的实现类ViewTarget中,会通过view.setTag()方法实现与Request的绑定,避免了同一个View的重复请求;通过抽象出一个ModelLoaderDataFetcher使使用者能够任意自定义底层数据请求的方式;通过抽象出Transformation使使用者能够在拿到最终数据之前进行自定义的处理。个人认为Glide优于Picasso的地方也在这里,就是极致的可扩展性。这部分原理靠文字是无法完全描述清楚的,还是需要从源码中寻找答案。