盒子
盒子
文章目录
  1. ModelLoader的查询与注册
  2. DataFetcher
  3. LoadPath
  4. 总结

Glide源码探究(三) - 网络资源加载

系列文章:

接着上篇笔记,按照Glide的流程查询完内存缓存之后应该算是查询磁盘缓存,但是由于磁盘缓存的数据依赖第一次请求的时候从网络下载,再写入磁盘缓存。所以我这篇先讲网络请求部分的流程。

这部分的代码坦白讲比较多也比较绕,我在看的时候也看的头晕。这里就不去把代码都列出来了,感兴趣的同学可以跟着下面的时序图去追踪一下:

EngineJob的功能比较简单,就是管理加载和回调:

1
2
3
4
5
6
7
/**
* A class that manages a load by adding and removing callbacks for for the load and notifying
* callbacks when the load completes.
*/
class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
...
}

它最重要的功能是启动了DecodeJob去加载资源,DecodeJob是一个运行在子线程的Runnable。它会使用Generator去从缓存或者数据源加载数据,我们这次只看从数据源加载的SourceGenerator。

SourceGenerator在启动的时候会使用DecodeHelper去获取一个叫LoadData的东西,这一步比较有意思我们展开讲,先看看DecodeHelper.getLoadData的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);

for (int i = 0, size = modelLoaders.size(); i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
if (current != null) {
loadData.add(current);
}
}
}
return loadData;
}

loadData是个List,而且DecodeHelper内部做了缓存。它的加载逻辑是先用model(就是我们load传入的url)从glideContext里面查询ModelLoader列表,然后遍历它去buildLoadData丢到loadData这个列表里面。

ModelLoader的查询与注册

getModelLoaders的逻辑很简单,就用modelLoaderRegistry去getModelLoaders:

1
2
3
4
@NonNull
public <Model> List<ModelLoader<Model, ?>> getModelLoaders(@NonNull Model model) {
return modelLoaderRegistry.getModelLoaders(model);
}

modelLoaderRegistry里面会根据model的class查询modelLoaders列表,然后遍历它去使用ModelLoader.handles方法判断这个ModelLoader是否支持这个model,如果是的再放到filteredLoaders里面一起返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
...
int size = modelLoaders.size();
boolean isEmpty = true;
List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();
for (int i = 0; i < size; i++) {
ModelLoader<A, ?> loader = modelLoaders.get(i);
if (loader.handles(model)) {
if (isEmpty) {
filteredLoaders = new ArrayList<>(size - i);
isEmpty = false;
}
filteredLoaders.add(loader);
}
}
...
return filteredLoaders;
}

这个逻辑要怎么理解呢?举个例子,就是假设model是一个Uri,那么getModelLoadersForClass查出来的ModelLoader列表里面可能有加载本地图片的也可能有加载网络图片的。然后分别调用ModelLoader.handles方法去过滤。如果是个本地请求的Uri,网络请求的ModelLoader就会被过滤掉,因为它只支持http和https的scheme:

1
2
3
4
5
6
7
8
9
10
public class UrlUriLoader<Data> implements ModelLoader<Uri, Data> {
private static final Set<String> SCHEMES =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("http", "https")));
...
@Override
public boolean handles(@NonNull Uri uri) {
return SCHEMES.contains(uri.getScheme());
}
...
}

getModelLoadersForClass方法里面也比较绕,我就不展开代码了,只要知道它是根据model的class去查询即可。

有了查询就肯定有注册,它的注册在Glide的构造函数里面有很长的一坨,第一个参数就是用于对比model class的,第二个参数是DataFetcher的数据类型,最后一个参数是ModelLoader的工厂:

1
2
3
4
5
6
7
8
9
10
registry
...
.append(String.class, InputStream.class, new StringLoader.StreamFactory())
...
.append(Uri.class, InputStream.class, new UriLoader.StreamFactory(contentResolver))
.append(Uri.class,ParcelFileDescriptor.class,new UriLoader.FileDescriptorFactory(contentResolver))
.append(Uri.class,AssetFileDescriptor.class,new UriLoader.AssetFileDescriptorFactory(contentResolver))
.append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory())
.append(URL.class, InputStream.class, new UrlLoader.StreamFactory())
...

光看这个流程其实还比较好理解,但是由于我们使用的是String类型的url,当我第一次追踪代码的时候还是有被绕晕。原因是String.class注册的StringLoader自己并不干活,而是将String转换成Uri再去registry里面找人干活:

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
public class StringLoader<Data> implements ModelLoader<String, Data> {
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);
}

@Override
public boolean handles(@NonNull String model) {
return true;
}
...
public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {
public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
}
...
}
...
}

可以看到StreamFactory创建StringLoader的时候调用了multiFactory.build(Uri.class, InputStream.class)方法创建了一个ModelLoader传给StringLoader。这个build出来的是一个MultiModelLoader,具体细节我也不讲了。它从registry查询了append时候第一个参数为Uri.class,第二个参数为InputStream.class的ModelLoader。

StringLoader.handles直接返回true,然后buildLoadData的时候在从这堆ModelLoader使用handles然后build出来。

也就是相当于将String.class的model转换成了Uri.class类型。但是这样还没有完,Uri.class最终又被转换成了GlideUrl.class,这个我就不展开代码了…

DataFetcher

得到的ModelLoader回到到DecodeHelper.getLoadData去buildLoadData创建LoadData,就得到了一个LoadData列表。

然后SourceGenerator.startNext里面就会遍历这个列表去找到一个能加载资源的LoadData,其中主要干活的是DataFetcher:

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean startNext() {
...
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;
}

startNextLoad里面使用loadData.fetcher启动资源的加载,完成后回调onDataReadyInternal:

1
2
3
4
5
6
7
8
9
10
11
12
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);
}
}
...);
}

但是值得注意的是Fetcher的加载完成并不是把图片文件下载完成,只是打开了文件流而已,需要等待后面的流程从里面读取:

1
2
3
4
5
6
7
8
public class HttpUrlFetcher implements DataFetcher<InputStream> {
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
...
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
...
}
}

LoadPath

看回时序图可以知道,DataFetcher的数据是被LoadPath读取去解码的。这里面也很复杂,但是我们并不需要全部了解,我这只讲个比较重要的东西。

LoadPath.load方法最终会调用到LoadPath.decodeResourceWithList方法。顾名思义它是遍历解码器列表查找一个解码器去解码文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Resource<ResourceType> decodeResourceWithList(...) throws GlideException {
Resource<ResourceType> result = null;

for (int i = 0, size = decoders.size(); i < size; i++) {
ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
...
DataType data = rewinder.rewindAndGet();
if (decoder.handles(data, options)) {
data = rewinder.rewindAndGet();
result = decoder.decode(data, width, height, options);
}
...

if (result != null) {
break;
}
}
...
return result;
}

这些解码器是哪里注册的呢?答案还是Glide的构造函数里面那坨很长的append,没错那坨append不仅注册了ModelLoader还注册了解码器:

1
2
3
4
5
6
registry
.append(ByteBuffer.class, new ByteBufferEncoder())
.append(InputStream.class, new StreamEncoder(arrayPool))
.append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
.append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
...

总结

之后的流程就是不断回到到Engine去放到弱引用缓存里面的。到这里整个网络资源的下载解码流程也就讲完了,我们来看看简化之后的时序图: