强大的Glide图片处理框架

强大的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
56
private 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的时候可以自定义 GlideModuleAndroidManifest 中声明也可以在创建继承 AppGlideModule 的Module类来进行设置(此处为4.0特性,通过apt解析Annotation得到类)。继承AppGlideModule类的GLideModule可以设置是否需要读取在AndroidManifest上配置的GlideModule。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
override fun applyOptions(context: Context, builder: GlideBuilder) {
//设置内存缓存大小
val maxMemory = Runtime.getRuntime().maxMemory().toInt()//获取系统分配给应用的总内存大小
val memoryCacheSize = maxMemory / 8L//设置图片内存缓存占用八分之一
builder.setMemoryCache(LruResourceCache(memoryCacheSize))

val diskCacheSize = 1024 * 1024 * 50L//最多可以缓存多少字节的数据
val path = FileUtils.cacheDir
builder.setDiskCache(DiskLruCacheFactory(path, "glide", diskCacheSize))
//builder.setDiskCache(new InternalCacheDiskCacheFactory(context,"cacheDirectoryName", 200*1024*1024));//设置文件缓存的大小为200M
//builder.setMemoryCache(new LruResourceCache(yourSizeInBytes));//设置内存缓存大小
//builder.setBitmapPool(new LruBitmapPool(sizeInBytes));//bitmap池大小
//builder.setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888);//bitmap格式 default is RGB_565
}

GlideModule 中可以设置Glide的一些配置,比如内存大小,缓存文件保存位置,图片默认解码类型等。

GlideModule 中除去设置配置属性外,也可以注册自定义组件,属于GlideModule中的第二个方法中

1
2
3
4
5
@Override
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 对象等等。

  1. Engine对象
    在创建Engine对象时传递了几个重要的参数,分别是线程池,内存缓存和硬盘缓存对象等。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    engine =
    new Engine(
    memoryCache,
    diskCacheFactory,
    diskCacheExecutor,
    sourceExecutor,
    GlideExecutor.newUnlimitedSourceExecutor(),
    GlideExecutor.newAnimationExecutor(),
    isActiveResourceRetentionAllowed);

Engine是一个非常重要的对象,后面扮演着重要的角色。

  1. RequestManagertRetriever 对象
    当开发者不传入自定义的RequestManagertFactory时,就会使用Glide自身默认的DEFAULT_FACTORY.
    1
    2
    3
    4
    5
    6
    7
    8
    private static final RequestManagerFactory DEFAULT_FACTORY = new RequestManagerFactory() {
    @NonNull
    @Override
    public RequestManager build(@NonNull Glide glide, @NonNull Lifecycle lifecycle,
    @NonNull RequestManagerTreeNode requestManagerTreeNode, @NonNull Context context) {
    return new RequestManager(glide, lifecycle, requestManagerTreeNode, context);
    }
    };

而其中的 RequestManager 就是Glide请求的一个管理器。

  1. 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
21
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;

@NonNull
@Deprecated
public <TResource> Registry register(
@NonNull Class<TResource> resourceClass, @NonNull ResourceEncoder<TResource> encoder) {
return append(resourceClass, encoder);
}

@NonNull
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和View

1
2
3
4
5
with(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

  1. 当传入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
    @NonNull
    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的加载也会同时终止。

  1. 非Application且在主线程中
    此时不论传入Activity、FragmentActivity、Fragment最终都会调用supportFragmentGetfragmentGet方法,而这两个方法最终流程都是一致的那就是会向当前的Activity当中添加一个隐藏的Fragment。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @NonNull
    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
6
into()  //传入 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
2
3
4
5
6
7
8
接上面
-> decodeJob.run() //真正线程执行的地方
-> getNextGenerator() //getNextGenerator方法中根据stage值来创建对象,返回的是SourceGenerator
-> sourceGenerator.startNext() //这里通过glideContext.getRegistry().getModelLoaders(model)方法获取合适的Loader来进行加载。
-> HttpGlideUrlLoader.LoadData() //如果传入的是url,此时这里就会返回这个Loader
-> HttpUrlFetcher.loadDataWithRedirects() //这里就是利用UrlConnection进行的网络请求
-> decodeFromRetrievedData() //数据请求结束后开始解码
-> Registry.getDecodePaths // 还是通过要转换成的类型如 Drawable.class 从 Registry 中获取相应的解码器,如普通图片的是 BitmaoDrawableTranscoder

真正解码过程
①:这里首先遍历decoders集合,分别的获取到ResourceDecoder解码器,还记得我们的decoders都包含哪些解码器吗?没错主要包含两种:BitmapDrawable.class和GifDrawable.class。不记得的往上面翻下,上面已详细的讲解过了。

②:然后通过rewinder.rewindAndGet()获取我们的InputStream数据流:

③:通过decoder.handles(data, options)方法来过滤掉不相匹配的解码器,最后解码放回Resouce。

总结

Glide 是一个很复杂的库,很多细节的部分想要完全理解是很花时间的,所以这里我总结一下比较重要的设计理念。

  1. Glide中通过自己添加一个隐藏的Fragment以实现图片加载的生命周期化
  2. Glide缓存分为两个部分,一部分为内存缓存另一部分为磁盘缓存。在内存缓存中,大部分缓存是放在以LruCache为基础的Map中,但是当图片在被使用时是会移动ActiveResource中避免在LruCache被回避。在磁盘缓存中,有四种缓存策略:无缓存,只缓存缩略图,只缓存原图,缓存原图和缩略图。
  3. 通过 Registry 注册方法灵活的实现数据的转码策略。
文章作者: cpacm
文章链接: http://www.cpacm.net/2018/06/11/Android进阶之路——强大的Glide图片处理框架/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 cpacm
打赏
  • 微信
  • 支付宝

评论