Glide源码导读

最近比较无聊,为了找点事干,就花了两天时间把Glide的源码大概看了一下。刚开始看Glide的源码头脑还是比较乱的,因为作者引入了几个概念,又大量用了泛型,如果不了解这些概念读起代码来就比较痛苦,我也没有详细看各种实现细节的东西,只是了解了下这个框架的大概样子,在这篇文章里,我会介绍下Glide中的一些关键概念,并走一遍图片加载流程,如果你要阅读Glide源码的话,应该多少会有点帮助。

基本概念

首先是三个最基本的概念:Model, DataResource

想一下,我们加载图片需要什么?一般是一个url,但url并不是所有情况,还有资源ID,文件等等,甚至可以是Feed流中的一条Feed,虽然一般我们会从Feed中取出图片的url来转换为从url中加载的情况,Glide把这些抽像为了一个概念,就是Model,所以Model就是数据地址的最初来源。

Model并不能直接解析为图片,比如一个url,是要转换为网络流的InputStream才能被解析为图片的,Model需要进行一次转换才能做为数据解析的数据源,这些转换后的东西就叫做Data,Glide并没有一个Data类,但有很多和它相关的概念,如dataClase,DataFetcher等。

那么Resource呢,其实它就是一个包装类,一个wrapper,它wrap一个对象,使这个对象可以通过对象池进行缓存与重用。

这三个基本概念介绍完了,接下来看一下Glide基本框架。

做为一个图片加载框架,肯定会包含缓存部分。

可以从网上很容易的了解到,Glide的磁盘缓存可以缓存原始数据,也可以缓存处理过的数据。什么意思呢,就是你有一张1000x1000的图片,但你是在列表中展示的,比如是200x200,那么缓存时可以直接将整个网络流缓存下来,即1000x1000的图片,要展示的时候再缩放,但这就降低了展示效率,所以Glide也可以把处理过的200x200的图片缓存起来,增加了缓存大小,但优化了展示速度。

至于怎么把数据缓存到磁盘,就引入了一个叫Encoder的概念,Encoder是用来持久化数据的。

但看源码时你会发现,Glide中有一个类叫Registry,可以注册多个Encoder,但你会发现它还可以注册ResourceEncoder。这两个Encoder很容易引起混淆,而其实ResouseEncoder继承自EncoderEncoder是用来持久化Data的,ResourceEncoder是用来持久化Resource的。看Glide默认注册的Encoder就知道了,默认注册的EncoderByteBufferInputStream,而ResourceEncoderBitmapBitmapDrawableGifDrawable,也就是一个持久化原始数据,一个持久化处理过的数据。我感觉把Encoder做为一个上级的抽象,引入一个和ResourceEncoder同级的DataEncoder就好理解了,正好和前面的基本概念DataResource对应。

Encoder就有Decoder,对应的类叫ResourceDecoder,用来将数据(InputStream等)解析为Resource

图片加载出来后还可能会应用各种变换,如圆角图片,圆形图片,处理这部分工作的叫Transformation

基础概念介绍的差不多了,加载流程也差不多出来了:

sequence1

但我们发现前面的介绍中少了一环,即:Glide是怎么把Model转换为Data的。这就引入另一个概念,ModelLoader,就是把Model转换成Data的,为了方便说明,直接把这个类的代码贴上来了,去掉了一些注释。

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
/**
* A factory interface for translating an arbitrarily complex data model into a concrete data type
* that can be used by an {@link DataFetcher} to obtain the data for a resource represented by the
* model.
*
* @param <Model> The type of the model.
* @param <Data> The type of the data that can be used by a
* {@link com.bumptech.glide.load.ResourceDecoder} to decode a resource.
*/
public interface ModelLoader<Model, Data> {
/**
* Contains a set of {@link com.bumptech.glide.load.Key Keys} identifying the source of the load,
* alternate cache keys pointing to equivalent data, and a
* {@link com.bumptech.glide.load.data.DataFetcher} that can be used to fetch data not found in
* cache.
*
* @param <Data> The type of data that well be loaded.
*/
class LoadData<Data> {
public final Key sourceKey;
public final List<Key> alternateKeys;
public final DataFetcher<Data> fetcher;
public LoadData(Key sourceKey, DataFetcher<Data> fetcher) {
this(sourceKey, Collections.<Key>emptyList(), fetcher);
}
public LoadData(Key sourceKey, List<Key> alternateKeys, DataFetcher<Data> fetcher) {
this.sourceKey = Preconditions.checkNotNull(sourceKey);
this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
this.fetcher = Preconditions.checkNotNull(fetcher);
}
}
LoadData<Data> buildLoadData(Model model, int width, int height, Options options);
boolean handles(Model model);
}

ModelLoader有两个方法,一个handles表示是否可以处理这个类型的Model,如果可以的话就可以通过buildLoadData生成一个LoadData,而LoadData包含了要用来做缓存的key,及用来获取数据的DataFetcher

到这里,整个加载流程就清楚了:

sequence2

基本加载流程

接下来要做的就是根据我们的使用方法走一遍流程,调用如下:

1
2
3
4
Glide.with(mContext)
.load(url)
.apply(RequestOptions.placeholderOf(R.drawable.loading))
.into(myImageView);

一步步看,先是Glide.with(mContext)

1
2
3
4
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}

通过RequestManagerRetriever获取到了一个RequestManager,至于为什么还需要一个RequestManagerRetriever并有各种重载方法,主要是因为Glide通过SupportRequestManagerFragmentRequestManagerFragment关联了Activity或Fragment的生命周期,用来做pauseRequests等操作。

然后是load

1
2
3
4
5
6
7
8
9
10
11
public RequestBuilder<Drawable> load(@Nullable Object model) {
return asDrawable().load(model);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class).transition(new DrawableTransitionOptions());
}
public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide.getGlideContext(), this, resourceClass);
}

asDrawable.load(model)的缩写,就是说这个Model我是要加载为Drawable的,最终返回一个RequestBuilder,看名字就知道是做什么了,不过这个类主要是设置Thumbnail Request,Transition等个别设置(旧版本中placeHolder等也是在这里设置的),大部分设置在RequestOptions里,这就是下面这一句:

1
apply(RequestOptions.placeholderOf(R.drawable.loading))

应用一个RequestOptionsRequestOptions可以设置各种请求相关的选项,如占位图片,加载失败的图片,缓存策略等。RequestOptions继承自BaseRequestOptions,但全是工厂方法生成各种RequestOptions。

最后就是into了,把图片加载到一个Target中。

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
public Target<TranscodeType> into(ImageView view) {
...
return into(context.buildImageViewTarget(view, transcodeClass));
}
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request previous = target.getRequest();
if (previous != null) {
requestManager.clear(target);
}
requestOptions.lock();
Request request = buildRequest(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}

Target是要加载到的目标,比如ImageViewTargetAppWidgetTarget,在这里我们传进来了一个ImageView,内部生成了一个DrawableImageViewTarget。这里最主要的操作是buildRequest然后交给RequestManagertrack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void track(Target<?> target, Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
// RequestTracker
public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}

TargetTracker主要就是记录一下所有正在加载的图片的Target,所以加载行为是在RequestTracker.runRequest中的,runRequest先判断是否是pause状态(RequestManager设置),如果不是就直接调用Request.begin触发加载,否则就回到pending队列里等待resume。

除了设置缩略图的情景,使用的Request都是SingleRequest,看一下它的begin方法:

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
public void begin() {
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}

加载逻辑是这几行:

1
2
3
4
5
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}

判断下是否知道Target的大小,如果大小已知就调用onSizeReady,否则就调用target.getSize获取它的大小,当成功获取到大小后,会通过回调继续调用onSizeReady,所以整个加载方法都是在onSizeReady里的。至于Target怎么获取它的大小,那要看它的实现了,对于ImageViewTarget,是通过ViewTreeObserver.OnPreDrawListener等到View要测绘的时候就知道它的大小了。

onSizeReady就是把操作转移到了Engine.load

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
public <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,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
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<R> engineJob = engineJobFactory.build(key, isMemoryCacheable,
useUnlimitedSourceExecutorPool);
DecodeJob<R> decodeJob = decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}

Engine.load中,先loadFromCache,如果缓存没有命中就再loadFromActiveResources,这是两级内存缓存,第一级是LruCache,第二级是ActiveCache,主要作用是,有可能一个图片很早就被加载了,可能已经从LruCache被移除掉了,但这个图片可能还在被某一个地方引用着,也就是还是Active的,那它就可能在将来仍被引用到,所以就把它保留在二级的ActiveCache中,ActiveCache中是以弱引用引用图片的,并通过ReferenceQueue监测弱引用的回收,然后用Handler.IdleHandler在CPU空闲时被被回收的引用项从ActiveCache中移除。

接下来看对应的Key是否已经正在加载,如果是的话,就addCallback,这样如果有多个地方同时请求同一张图片的话,只会生成一个加载任务,并都能收到回调,这点是比Universal-Image-Loader好的地方。

正常的加载流程是生成一个EngineJob和一个DecodeJob,通过engineJob.start(decodeJob)来进行实际的加载。

1
2
3
4
5
6
7
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}

EngineJob.start直接将DecodeJob交给Executor去执行了(DecodeJob实现了Runnable接口)。DecodeJob的加载操作放到了runWrapped

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
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);
}
}
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 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 Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}

主要加载逻辑就在这三个函数中了:

  1. 先获取当前的Stage
  2. 根据当前的Stage获取相应的Generator,
  3. 执行Generator

一共有三种Generator:

  • ResourceCacheGenerator:从处理过的缓存加载数据
  • DataCacheGenerator:从原始缓存加载数据
  • SourceGenerator:从数据源请求数据,如网络请求

前面说过,Glide的磁盘缓存可以选择缓存原始图片,缓存处理过的图片(如列表中显示缩略图时缩放后的图片),这三个Generator就分别对应处理过的图片缓存,原始图片缓存,和数据源加载。

在上面的第三步执行Generator时主要就是调用了Generator,其实就是执行Generator的startNext方法,这里以SourceGenerator为例。

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
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
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;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}

先忽略函数开始时dataToCachesourceCacheGenerator相关的代码,第一次加载时这两个一定是null的。剩下的流程就是获取一个LoadData,调用LoadData.fetcher.loadData加载数据。看一下LoadData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
int size = modelLoaders.size();
for (int i = 0; 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;
}

getLoadData中通过获取所有提前注册过的能处理Model类型的ModelLoader,调用它的buildLoadData生成LoadData,最终返回一个LoadData列表。

前面说过LoadData包含了用来获取数据的DataFetcherSourceGenerator.startNext就调用了loadData.fetcher.loadData来进行加载数据,并传进去一个Callback,就是当前的SourceGenerator,如果加载成功,会调用onDataReady

1
2
3
4
5
6
7
8
9
10
11
12
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}

数据加载成功后,如果设置了要进行磁盘缓存,会设置成员变量dataToCache,并调用Callback的reschedule,结果就是会再次调用当前Generator的startNextstartNext的前半部分实现就起作用了,会进行写缓存的操作。

rescheudle后写了缓存后,或不缓存的情况下,会调用onDataFetcherReady,这个Callback就是前面的DecodeJob,在onDataFetcherReady中会调用decodeFromRetrievedData decode数据,最终调用到decodeFromFetcher

1
2
3
4
5
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,并调用它的load方法。LoadPath就是封装了多个DecodePathDecodePath用于decode and Transform数据,如InputStream->Bitmap->BitmapDrawable,DecodePath中会获取预先注册的Decoder来decode获取到的数据,decode成功后通过回调调用DecodeJobonResourceDecoded方法。

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
public Resource<Z> onResourceDecoded(Resource<Z> decoded) {
Class<Z> resourceSubClass = getResourceClass(decoded);
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
transformed = appliedTransformation.transform(decoded, width, height); ////////////////////////// 1
}
// TODO: Make this the responsibility of the Transformation.
if (!decoded.equals(transformed)) {
decoded.recycle();
}
final EncodeStrategy encodeStrategy;
final ResourceEncoder<Z> encoder;
if (decodeHelper.isResourceEncoderAvailable(transformed)) {
encoder = decodeHelper.getResultEncoder(transformed);
encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
Resource<Z> result = transformed;
boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
encodeStrategy)) {
if (encoder == null) {
throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
}
final Key key;
if (encodeStrategy == EncodeStrategy.SOURCE) {
key = new DataCacheKey(currentSourceKey, signature);
} else if (encodeStrategy == EncodeStrategy.TRANSFORMED) {
key = new ResourceCacheKey(currentSourceKey, signature, width, height,
appliedTransformation, resourceSubClass, options);
} else {
throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
}
LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
deferredEncodeManager.init(key, encoder, lockedResult); ////////////////////////// 2
result = lockedResult;
}
return result;
}

在上述代码的注释1处对加载成功的资源应用Transformation,然后在注释2处根据缓存策略初始化DeferredEncodeManager,在前面的decodeFromRetrievedData中,如果有必要会把transform过的资源写缓存。

1
2
3
4
5
6
7
8
9
private void decodeFromRetrievedData() {
...
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}

notifyEncodeAndRelease中处理了对处理过的图片的缓存操作。当缓存完成后(如果有需要的话)就通过回调告诉外面加载完成了。至此,整个加载过程完成。

Glide配置

Glide允许我们进行一定程度的自定义,比如设置自定义的Executor,设置缓存池,设置Log等级等,完成这个任务的类叫GlideBuilder,Glide类在工程中是作为单例使用的,看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Glide get(Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
GlideBuilder builder = new GlideBuilder(applicationContext);
for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
glide = builder.createGlide();
for (GlideModule module : modules) {
module.registerComponents(applicationContext, glide.registry);
}
}
}
}
return glide;
}

通过GlideBuilder生成了一个Glide实例,我们是没有办法直接配置GlideBuilder的,但我们发现Glide.get解析了Manifest,获取了一个GlideModule的列表,并调用了它的applyOptionsregisterComponents方法。以项目中OkHttp的配置为例

1
2
3
4
5
6
7
8
9
10
11
public class OkHttpGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// Do nothing.
}
@Override
public void registerComponents(Context context, Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}

GlideModule有两个方法,applyOptions,有一个GlideBuilder参数,在这里我们就可以配置Glide了。还有一个registerComponents方法,并有一个Registry参数,通过这个类的实例我们就可以注册我们自定义的ModelLoaderEncoder等基础组件了。

自定义GlideModule是通过Manifest的meta-data标签配置的

1
2
3
<meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule"/>

参考资料

http://www.lightskystreet.com/2015/10/12/glide_source_analysis/

作者:AngelDevil
出处:http://www.angeldevil.me
原文链接:http://www.angeldevil.me/2016/09/05/glide/

转载请注明出处!