强大的Glide图片处理框架
简介
Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。
Glide 支持拉取,解码和展示视频快照,图片,和GIF动画。Glide的Api是如此的灵活,开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。
1 | Glide.with(fragment).load(url).into(imageView); |
Glide使用简明的流式语法API,在大部分情况下一行代码搞定图片加载。
Glide 充分考虑了Android图片加载性能的两个关键方面:
- 图片解码速度
- 解码图片带来的资源压力
为了让用户拥有良好的App使用体验,图片不仅要快速加载,而且还不能因为过多的主线程I/O或频繁的垃圾回收导致页面的闪烁和抖动现象。
Glide使用了多个步骤来确保在Android上加载图片尽可能的快速和平滑:
- 自动、智能地下采样(downsampling)和缓存(caching),以最小化存储开销和解码次数;
- 积极的资源重用,例如字节数组和Bitmap,以最小化昂贵的垃圾回收和堆碎片影响;
- 深度的生命周期集成,以确保仅优先处理活跃的Fragment和Activity的请求,并有利于应用在必要时释放资源以避免在后台时被杀掉。
官方文档:快速高效的Android图片加载库
重要设计原理
首先Glide会根据with传入的(context)绑定自己内部的生成Fragment的内部LifeCycle,以保证能跟随其生命周期加载图片,停止图片加载(ApplicationContext无法与生命周期绑定)。
Glide 缓存
glide缓存分为两个部分,一部分为内存缓存另一部分为磁盘缓存。在内存缓存中,大部分缓存是放在以LruCache为基础的Map中,但是当图片在被使用时是会移动ActiveResource中避免在LruCache被回避。在磁盘缓存中,有四种缓存策略:无缓存,只缓存缩略图,只缓存原图,缓存原图和缩略图。
初始化
Glide初始化过程主要在 Glide 类里面的initializeGlide
方法中进行。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
56private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
...
// 先获取AppGlideModule
GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();
List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled(){
//获取 manifest 上 GlideModule
manifestModules = new ManifestParser(applicationContext).parse();
}
if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
Set<Class<?>> excludedModuleClasses =
annotationGeneratedModule.getExcludedModuleClasses();
Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
while (iterator.hasNext()) {
com.bumptech.glide.module.GlideModule current = iterator.next();
if (!excludedModuleClasses.contains(current.getClass())) {
continue;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "AppGlideModule excludes manifest GlideModule: " + current);
}
iterator.remove();
}
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
for (com.bumptech.glide.module.GlideModule glideModule : manifestModules) {
Log.d(TAG, "Discovered GlideModule from manifest: " + glideModule.getClass());
}
}
// 每个GlideModule调用它们内部的两个方法 applyOptions 和 registerComponents
RequestManagerRetriever.RequestManagerFactory factory =
annotationGeneratedModule != null
? annotationGeneratedModule.getRequestManagerFactory() : null;
builder.setRequestManagerFactory(factory);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.applyOptions(applicationContext, builder);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.applyOptions(applicationContext, builder);
}
// 利用Builder构建Glide
Glide glide = builder.build(applicationContext);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.registerComponents(applicationContext, glide, glide.registry);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
}
applicationContext.registerComponentCallbacks(glide);
Glide.glide = glide;
}
如我们所知,在使用Glide的时候可以自定义 GlideModule
在 AndroidManifest
中声明也可以在创建继承 AppGlideModule
的Module类来进行设置(此处为4.0特性,通过apt解析Annotation得到类)。继承AppGlideModule类的GLideModule可以设置是否需要读取在AndroidManifest上配置的GlideModule。
1 | override fun applyOptions(context: Context, builder: GlideBuilder) { |
GlideModule 中可以设置Glide的一些配置,比如内存大小,缓存文件保存位置,图片默认解码类型等。
GlideModule 中除去设置配置属性外,也可以注册自定义组件,属于GlideModule中的第二个方法中1
2
3
4
5
public void registerComponents(@NonNull Context context, @NonNull Glide glide,
@NonNull Registry registry) {
// Default empty impl.
}
注册自定义的组件的作用后面再说。
在调用GlideModule的 registerComponents
方法之前会通过Builder生成Glide实例。
Builder.build()
方法中主要是构建线程池(包括sourceExecutor ,diskCacheExecutor ),缓存大小和缓存器,默认的连接监听工厂(connectivityMonitorFactory ),Engine对象和RequestManagerRetriever 对象等等。
- Engine对象
在创建Engine对象时传递了几个重要的参数,分别是线程池,内存缓存和硬盘缓存对象等。1
2
3
4
5
6
7
8
9engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
Engine是一个非常重要的对象,后面扮演着重要的角色。
- RequestManagertRetriever 对象
当开发者不传入自定义的RequestManagertFactory时,就会使用Glide自身默认的DEFAULT_FACTORY.1
2
3
4
5
6
7
8private static final RequestManagerFactory DEFAULT_FACTORY = new RequestManagerFactory() {
public RequestManager build(@NonNull Glide glide, @NonNull Lifecycle lifecycle,
@NonNull RequestManagerTreeNode requestManagerTreeNode, @NonNull Context context) {
return new RequestManager(glide, lifecycle, requestManagerTreeNode, context);
}
};
而其中的 RequestManager 就是Glide请求的一个管理器。
- Register对象
GLide中的 Register 主要是添加很多的注册或解析方式,这在后面用来解析是从内存,文件或是网络获取资源有着重要的作用,而且它每一类解析方式都会提供多个方法,一种方式获取不到将会使用另外一种,知道获取到资源为止,来看下它的register和append方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21private 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;
public <TResource> Registry register(
@NonNull Class<TResource> resourceClass, @NonNull ResourceEncoder<TResource> encoder) {
return append(resourceClass, encoder);
}
public <TResource> Registry append(
@NonNull Class<TResource> resourceClass, @NonNull ResourceEncoder<TResource> encoder) {
resourceEncoderRegistry.append(resourceClass, encoder);
return this;
}
不同的注册方法对应着不同的 Register 对象体。
with()方法
Glide.with()是Glide的一组静态方法,有好几个重载方法,能够传入参数有 Activity,Context,FragmentActivity,Fragment和View1
2
3
4
5with(android.app.Activity)
with(android.app.Fragment)
with(android.support.v4.app.Fragment)
with(android.support.v4.app.FragmentActivity)
with(android.view)
每一个with()方法重载的代码都非常简单,都是调用 getRetriever(activity).get(activity)
,返回一个RequestManager对象。在这里我们将入参分为两类,一类是Application,
一类是非Application
- 当传入Application 或在非主线程中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private RequestManager getApplicationManager(@NonNull Context context) {
// Either an application context or we're on a background thread.
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
// Normally pause/resume is taken care of by the fragment we add to the fragment or
// activity. However, in this case since the manager attached to the application will not
// receive lifecycle events, we must force the manager to start resumed using
// ApplicationLifecycle.
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context.getApplicationContext());
applicationManager =
factory.build(
glide,
new ApplicationLifecycle(),
new EmptyRequestManagerTreeNode(),
context.getApplicationContext());
}
}
}
return applicationManager;
其实这是最简单的一种情况,因为Application对象的生命周期即应用程序的生命周期,因此Glide并不需要做什么特殊的处理,它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide的加载也会同时终止。
- 非Application且在主线程中
此时不论传入Activity、FragmentActivity、Fragment最终都会调用supportFragmentGet
或fragmentGet
方法,而这两个方法最终流程都是一致的那就是会向当前的Activity当中添加一个隐藏的Fragment。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private RequestManager supportFragmentGet(
@NonNull Context context,
@NonNull FragmentManager fm,
@Nullable Fragment parentHint,
boolean isParentVisible) {
SupportRequestManagerFragment current =
getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
Glide通过添加隐藏Fragment的方法以获知Activity的生命周期,因为Fragment可以感知Activity的生命周期,这种技巧也可以在RxPermissions
这个开源库中看到。
load()
在调用with方法获取到RequestManager对象的前提下,调用load方法,并传递url参数.
这里并没有直接的去加载url获取资源,而是首先调用asDrawable方法来配置图片的信息,其实就是说加载获取的图片资源是转换为drawale或是bitmap或是gif进行图片显示,默认的是使用drawale,也可以使用asGif()/asBitmap()来设置它是已什么形式来展示。load方法目的是为了获取RequestBuilder对象。完成asDrawable方法对RequestBuilder的创建后才调用load方法来传递我们的url地址。
在 RequestBuilder 对象中,可以通过不停调用 RequestBuilder 对象中的方法赋予自己想要的一些设置,如apply(RequestOptions)
,transition(TransitionOptions)
,listener(RequestListener)
,error(errorBuilder)
和thumbnail(RequestBuilder)
等。
load()本质是生成一个RequestBuilder并赋予相应的属性和设置。
into()
在 into() 内容过于繁多,我这里就简单介绍整个流程。1
2
3
4
5
6into() //传入 ImageView,并通过ImageViewTargetFactory将其转化为 ViewTarget
-> buildRequest() // 通过一系列的跳转最终调用到 `SingleRequest.obtain`方法返回SingleRequest。
-> requestManaget.track(target,request) //通过RequestManager启动request
-> request.begin() //判断model是否为空为空直接调用onLoadFailed加载失败
-> onSizeReady() // 获取ImageView的宽高来觉得要加载图片的尺寸
-> engine.load() // 所有重要的地方都在这里了。
先来梳理下 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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98/**
①:判断是否在主线程运行,说明到目前为止还是在主线程执行的,并没有真正的开启子线程。
②:通过keyFactory工厂来构建一个EngineKey对象,key关联着model,也就是url,它很根据model,view的宽高等等属性来构建一个EngineKey对象,这个对象可以用来指定缓存地址,可以用来从缓存中查找资源等。
③:根据创建的key对象分别调用loadFromCache和loadFromActiveResources方法来从内存中查找是否有缓存资源,如果有,则回调cb.onResourceReady来直接设置图片了。
④:分别使用engineJobFactory和decodeJobFactory构建EngineJob和DecodeJob对象,这两个对象是真正的加载资源的两个重要类,EngineJob对象负责开启线程去加载资源,并且加载得资源后转换到主线程并进行回调;DecodeJob是真正的执行者,它就是去网络加载资源的地方,EngineJob开启线程,真正执行的是DecodeJob,DecodeJob之后完毕之后叫道EngineJob去分发回调。这就是这两个类的关系。
⑤:EngineJob和DecodeJob的构建是基本一致的,我们看看比较复杂的DecodeJob的构建:在build方法中,首先通过pool来创建一个DecodeJob对象,然后调用DecodeJob对象的init方法进行初始化,在初始化中值得注意的是调用了decodeHelper对象的init方法。decodeHelper方法是DecodeJob的重要辅助类,后面我们会详细的接触它。
⑥:上面也提到回调,这里先cb添加到engineJob.addCallback();中,然后调用EngineJob的start方法来开启线程。
**/
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,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// 其中内存的缓存策略也作用在这里
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;
}
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;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
1 | 接上面 |
真正解码过程
①:这里首先遍历decoders集合,分别的获取到ResourceDecoder解码器,还记得我们的decoders都包含哪些解码器吗?没错主要包含两种:BitmapDrawable.class和GifDrawable.class。不记得的往上面翻下,上面已详细的讲解过了。
②:然后通过rewinder.rewindAndGet()获取我们的InputStream数据流:
③:通过decoder.handles(data, options)方法来过滤掉不相匹配的解码器,最后解码放回Resouce。
总结
Glide 是一个很复杂的库,很多细节的部分想要完全理解是很花时间的,所以这里我总结一下比较重要的设计理念。
- Glide中通过自己添加一个隐藏的Fragment以实现图片加载的生命周期化
- Glide缓存分为两个部分,一部分为内存缓存另一部分为磁盘缓存。在内存缓存中,大部分缓存是放在以LruCache为基础的Map中,但是当图片在被使用时是会移动ActiveResource中避免在LruCache被回避。在磁盘缓存中,有四种缓存策略:无缓存,只缓存缩略图,只缓存原图,缓存原图和缩略图。
- 通过 Registry 注册方法灵活的实现数据的转码策略。