原文地址

转载于公众号(一个很不错的公众号):

一、前言

Glide 现在大范围的使用在各种商业项目中,而对于一般而言, Glide 的 Api 封装的非常好,多数情况下我们只需要使用它,在使用的基础之上,才考虑如何了解它。

本文的目的是让你如何快速上手 Glide 3.x ,来快速投入开发,本文力求做到快速上手,所以只讲在上手的时候,你需要关注的。

二、简单使用

2.1 什么是 Glide ?

既然要用到 Glide ,那就先简单的介绍一下 Glide 。

Glide 简单来说就是一个 Google 主导的图片加载开源库。它稳定、速度快、可自适应图片尺寸、支持众多格式、支持加载不同来源的图片、内存和磁盘缓存的优化。这些,都是它的好处(当然不止这些),这里就不一一细说了。

你只需要知道,它是一款主流的图片加载库即可,它包含了你能想到的所有功能,并且支持扩展。

Glide 的 Github 地址

2.2 在项目内集成 Glide

虽然它已经到 v4.x 了,但是本文还是就最常用的 v3.8.0 版本的集成,做一个简单的介绍。

集成的方式有多种,可以直接引用 jar 包,也可以使用 Maven,这里还是使用主流的 Gradle 来集成它。

1
2
3
dependencies {
compile 'com.github.bumptech.glide:glide:3.8.0'
}

如果需要配置混淆,还需要在混淆文件中区分 Glide 。

1
2
3
4
5
-keep public class * implements com.bumptech.glide.module.GlideModule 
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}

配好 Glide ,我们就可以开始使用它了。

2.3 最简单的使用

Glide 是支持链式调用的,但是它不是简单的在每个方法中返回 this ,它更复杂一些,后面会讲到,所以通常使用它只需要使用一条语句即可。

1
2
ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
GlideApp.with(this).load("http://goo.gl/gEgYUd").into(imageView);

这是一个最简单的 Glide 的 Demo,使用它即可从网络上加载一张图片到一个 ImageView 中去显示。

三、Glide 需要了解的内容

前面的例子可以看到,实际上 Glide 的链式调用,它的主要方法就是三个,先来简单看看他们:

  • with:主要是传递一个 Glide 可用的 Context,它和生命周期相关。
  • load:接收一个待加载的图片资源,可支持多种格式。
  • into:指定加载的图片的最终使用目标对象,例如可以是一个 ImageView。

这三个主要的部分,贯穿了 Glide 使用的主要重点内容,接下来让我们好好看看他们。

3.1 with()

前面提到,这里的 with() 方法用于给 Glide 传递一个 Context 对象,它可以支持多种 Context:

对于 with 而言,它会返回一个 RequestManager 用于管理请求,而它接收的这些不同的 Context ,并不是为了让我们方便使用,而是会对当前 Context 的生命周期做监听,来管理 Glide 自身的图片加载的请求。

举个例子:当使用 with(Activity) 的时候,如果此时当前 Activity 被关闭掉了,那么 Glide 就会将这个 Activity 下所有的图片请求停止掉。也就是实现了 Glide 和 页面生命周期的绑定,来优化 Glide 自身的请求策略。

所以,在使用 Glide 的时候,尽量使用当前页面的 Activity ,而非直接传递一个 Context 进去。尽量小的选择 Context 的范围,他们的推荐优先级为:
Fragment->Activity->Context

3.2 load()

load() 方法,就是去指定一个待加载的资源,它支持很多格式和资源种类。例如:网络地址、本地文件、Drawable 等,它都是可以做到很好的加载的。

load() 方法,并不是在 Glide 中,前面也提到 with() 会返回一个 RequestManager 对象,load() 方法在它内部实现:

具体 load() 方法,支持的资源种类,可以看到它方法的重载,基本上我们能想到的,它都支持,它返回的是另外一个 DrawableTypeRequest 对象。

3.3 into()

into() 方法,用于指定加载的图片资源,最终给谁来使用。加载的图片,最终一定是用来显示的,所以它需要指定一个使用图片的对象。into() 实际上是 DrawableTypeRequest 中的方法,DrawableTypeRequest 是一个多层继承的类,它实际上自己是没有对 into() 方法的实现的,大部分实现都是在其父类 DrawableRequestBuilder 和 父类的父类 GenericRequestBuilder 中的:

从上可以看到 into() 不只是可以接受一个 ImageView ,也可以是一些其他的什么。这也很好理解,在项目内,也不仅仅只有 ImageView 可以用来显示图片,View 的 background 也是可以用于显示图片的。

四、Glide 的使用细节

既然 Glide 使用过程中,最重要的三个方法已经介绍过了,他们是 Glide 能完成功能的基础,接下来,就开始介绍 Glide 的使用细节,来见证 Glide 的强大。

本节介绍的 Glide 的使用细节,基本上都是与 load() 方法返回的 DrawableTypeRequest 对象进行操作,对其进行一些配置。

4.1 不同状态的占位图

在图片加载的过程中,会经历过多过程,例如:加载中、加载失败等等,在这些过程中,其实是可以为暂时为 ImageView 设置一个占位的的,来定制加载中、加载失败这种状态的显示效果。
Glide 定制的占位图,有三种:

  • placeholder :指定加载前显示的图片资源。
  • error:指定加载失败显示的图片资源。
  • fallback:指定传递加载资源为 null 的时候,显示的图片资源。


例如上面的例子中,其实 fallback() 是无需指定的,因为 imageUri 是不可能为 null 的。而其他的,都会在不同的阶段显示出来,加载前会显示 load_placeholder ,如果加载失败了,会显示 load_error 。不同状态的占位图,实际上是一种容错的表现,所以只能用于加载一个本地资源,允许传递一个 @DrawableId 或者 Drawable 对象。

4.2 缩放控制

某些时候,因为图片的尺寸和控件的尺寸,不一定能匹配,所以会对图片的显示效果,进行一些缩放,而大多数情况下,这种默认的缩放策略,并不是我们想要的,Glide 提供了一些方法来控制缩放的效果:

  • centerCrop()
  • fitCenter()

这两个方法和 ImageView.setScaleType() 中传递的参数效果类似,就不再一一赘述了。

4.3 缓存控制

现在基本上所有的图片加载库,都是遵照三级缓存的策略:网络、磁盘、内存,Glide 也是如此,为了更好的体验,这些缓存默认都是全部开启的。
1、内存缓存,有或没有,Glide 提供了一个 skipMemoryCache() 方法传递一个 Boolean 的值用于指定是否跳过磁盘缓存,默认是 false ,表示需要内存缓存。
2、磁盘缓存,为了保证效率,默认会去缓存多种尺寸的图片在磁盘上的,也就是说,对于同一个 Uri,如果在不同尺寸的 ImageView 中使用到它了,默认在设备的磁盘上也会有多张不同尺寸的图片。这样下次加载的时候速度更快,是一种以空间换效率的策略。
3、磁盘缓存,需要使用 diskCacheStrategy() 方法来改变它,它是一种比较复杂的策略,它需要传递一个 DiskCacheStrategy 的枚举类型:

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
/**
* Set of available caching strategies for media.
*/
public enum DiskCacheStrategy {
/** Caches with both {@link #SOURCE} and {@link #RESULT}. */
ALL(true, true),
/** Saves no data to cache. */
NONE(false, false),
/** Saves just the original data to cache. */
SOURCE(true, false),
/** Saves the media item after all transformations to cache. */
RESULT(false, true);

private final boolean cacheSource;
private final boolean cacheResult;

DiskCacheStrategy(boolean cacheSource, boolean cacheResult) {
this.cacheSource = cacheSource;
this.cacheResult = cacheResult;
}
...
```
可以看到,它实际上是通过两个参数来标记磁盘缓存的策略的。

* ALL:缓存所有类型的图片(默认行为)。
* NONE :禁用磁盘缓存。
* SOURCE : 只缓存全尺寸的原图。
* RESULT :只缓存压缩后的图片。

所以具体使用那种,就需要看当前加载的图片属于哪一种了。

```JAVA
ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
Glide.with(this)
.load("http://goo.gl/gEgYUd")
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.skipMemoryCache(true)
.into(imageView);

上面的例子就是忽略内存缓存,并且磁盘只缓存原图的策略。

4.4 加载优先级

对于同一个页面,如果需要在多个地方都加载线上图片,必然会存在一个优先级的问题。例如:正常来说,背景图是比其他图片优先级更高的图片。

Glide 是可以在加载中,对当前加载的图片,调整加载的优先级的。需要使用 priority() 方法,它可以接受一个 Priority 的枚举类型,包含四种值:LOW(低)、HIGH(高)、NORMAL(普通)、IMMEDIATE(立即)。

可以在我们需要的时候,对其进行配置,他只是用于 Glide 在加载图片的时候一个优化请求的参数而已,并不影响最终显示的顺序。

1
2
3
4
5
ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
Glide.with(this)
.load("http://goo.gl/gEgYUd")
.priority(Priority.IMMEDIATE)
.into(imageView);

4.5 载入动画

Glide 在显示图片的时候,为了让显示效果不那么突兀,会以一种更柔和的方式去显示,就会在加载的时候给一个动画效果,它可以使用 crossFade() 方法进行配置,如果不特殊处理,默认它是开启的,默认动画的时长是 300ms。

crossFade() 也是有多个重载的,主要是为了指定动画以及动画的时长。而 crossFade() 的源码中实际上只是对 animate() 方法的一个包装而已。crossFade() 的效果是默认开启的,所以如果不需要这个动画效果,可以使用 dontAnimate() 来禁用动画效果。

有一些情况下,crossFade() 方法并不能满足我们的需求。如果对加载的动画有特殊的定制需要,可以使用更灵活的 animate() 方法来自己实现动画。

可以看到,animate() 支持多种格式的动画的配置,对于动画的效果,这里就不一一讲解了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ViewPropertyAnimation.Animator animator = new ViewPropertyAnimation.Animator(){

@Override
public void animate(View view) {
view.setAlpha(0f);
ObjectAnimator fadeAnim = ObjectAnimator.ofFloat(view,"alpha",0f,1f);
fadeAnim.setDuration(2500);
fadeAnim.start();
}
};

ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
Glide.with(this)
.load("http://goo.gl/gEgYUd")
.animate(animator)
.into(imageView);

4.6 支持 Gif & 视频

Glide 的一个非常棒的功能,就是可以支持 Gif,并且使用起来和正常的想要加载一张网络上的图片,并没有什么区别。但是有时候我们需要对加载的 Gif 图做一个检查,例如校验它是否是一个 Gif ,如果不是,则认为是一次错误的加载。这个时候就可以使用 asGif() 来进行校验,如果当前加载的图片不是一个正确的 Gif 格式,则会去显示 error() 配置的图片。

1
2
3
4
5
6
ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
Glide.with(this)
.load("http://img4q.duitang.com/uploads/item/201502/28/20150228235225_BHEXZ.gif")
.asGif()
.error(R.mipmap.load_error)
.into(imageView);

当然,有时候我们可能只是为了显示一张图片,可以强制显示 Gif 图片的第一帧,使用 asBitmap() 方法标记即可(将 asGif() 替换成 asBitmap() 就可以了)。

Glide 对 Gif 的支持之外,提示还对 Video 格式的文件也进行了支持。但是它和 Gif 显示的效果不一样的一点在于,它并不会去播放视频文件,而只是将视频文件的第一帧做为一个图片去显示出来。如果依然想要播放一段视频文件,使用 Glide 不是一个好注意,你应该使用 VideoView。

其次 Glide 对视频的支持,仅限于本地视频,并无法对网络视频进行支持。

1
2
3
4
5
ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
String path = "/storage/emulated/0/Pictures/test.mp4";
Glide.with(this)
.load(Uri.fromFile(new File(path)))
.into(imageView);

4.7 加载监听

如果有对 Glide 加载的图片的结果进行监听的,可以使用 listener() 方法设置一个监听器,它接收一个 RequestListener 的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
RequestListener listener = new RequestListener<String, GlideDrawable>() {

@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
return false;
}

@Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
return false;
}
};

ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
Glide.with(this)
.load("http://goo.gl/gEgYUd")
.listener(listener)
.error(R.mipmap.load_error)
.into(imageView);

一般而言,如果我们需要监听图片加载错误的原因,可以在 onException() 中做处理。

需要注意的是,这两个方法的返回值,最好都是 false,因为如果返回 true ,将表示你已经处理了这次的事件,而 Glide 将不会再做额外的处理。例如,如果 onException() 返回了 true 的话,在图片加载失败之后,error() 中设置的图片,并不会被显示,因为 Glide 认为开发者已经在外部对这个错误进行了处理。

4.8 变换加载的图片

对于使用 Glide 加载的图片,如果想要在其显示之前,对其进行一些变换操作,例如,改变颜色、虚化、圆角子类的,都需要用到 transfrom() 方法,它主要用于支持在图片显示之前,自定义的变换效果。

变换有两个方法:

  • transfrom():它可以添加一个通用的变换效果。
  • bitmapTransfrom():限制了变换的类型,只能设置 Bitmap 的变换。

变换这种操作,其实定制性非常的强,展开讲就比较复杂了,只需要知道,Glide 是可以对加载的图片在显示之前进行一些预处理的操作的,在具体使用的时候再回头来看相关资料即可。

这里推荐一个开源的库,来支持大多数变换的效果。

Github 地址

4.9 into() 其他实现

前面所有的例子中,into() 方法作为 Glide 加载图片流程的最后一个环节,它不仅仅只能支持一个 ImageView。有时候我们还需要给 View 中设置一个背景的需要,这个使用 Glide 也是可以办到的,但是就需要用到 into() 方法的其他重载方法了。

撇开 into(ImageView) 不说,into(int,int) 实际上是一个指定尺寸的同步方法,可以在子线程中,通过它来得到一个 GlideDrawable 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Thread(new Runnable() {

@Override
public void run() {
try {
GlideDrawable drawable = Glide.with(MainActivity.this)
.load("http://goo.gl/gEgYUd")
.error(R.mipmap.load_error)
.into(100, 100)
.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();

但是这并不是很常用的场景,大部分我们还是使用泛型的方式来使用 Glide 的。

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
/**
* Set the target the resource will be loaded into.
*
* @see Glide#clear(com.bumptech.glide.request.target.Target)
*
* @param target The target to load the resource into.
* @return The given target.
*/
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;
}

它的完整签名可以看出,它实际上接收的是一个 Target 对象,而 Glide 同时也提供了非常多的 Target 的子类。

这些子类里面,有一些是不常用的,例如 AppWidgetTarget 和 NotificationTarget 就是为了 AppWidget 和 Notification 中加载图片准备的。这里只介绍两个比较常用的 Target :SimpleTarget 和 ViewTarget ,其实使用起来都是大同小异。

如果我们不关心图片加载的用途,只是单纯的需要加载一个 Bitmap 或者 Drawable ,就可以使用 SimpleTarget 来处理。

1
2
3
4
5
6
7
8
9
10
11
12
final ImageView imageView = (ImageView) findViewById(R.id.my_image_view);

SimpleTarget simpleTarget = new SimpleTarget<GlideDrawable>(100, 100) {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
imageView.setImageDrawable(resource);
}
};

Glide.with(this)
.load("http://goo.gl/gEgYUd")
.into(simpleTarget);

SimpleTarget 可以接受一个 GlideDrawable 或者 Bitmap 的类型作为加载的类型。如果需要指定加载的图片尺寸,还可以在构造方法中指定,如果不对其进行指定,则加载的是图片的原尺寸。

再来看看 ViewTarget, 从名称上可以才出来,它实际上是想让 Glide 加载一个图片资源给某个 View 使用。它可以解决有时候我们显示图片的 View 并不是一个 ImageView 的问题,也可能是一个 View 的背景。

1
2
3
4
5
6
7
8
9
10
11
12
final ImageView imageView = (ImageView) findViewById(R.id.my_image_view);

ViewTarget viewTarget = new ViewTarget<View,GlideDrawable>(imageView) {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
imageView.setImageDrawable(resource);
}
};

Glide.with(this)
.load("http://goo.gl/gEgYUd")
.into(viewTarget);

ViewTarget 需要指定 View 的类型,以及加载的资源类型,这里直接使用的 View 和 GlideDrawable ,然后将我们需要的使用图片的目标 View 当构造参数传递进去即可,最终它它会一个内部 view 变量去持有它,供之后使用,在 onResourceReady() 这个回调方法中,直接按我们的需要使用 GlideDrawable 和 View 即可。

如果是需要在非 ImageView 的其他 View 上使用图片,推荐使用 ViewTarget 。它内部是会去计算 View 的尺寸,来优化缓存的图片。和加载 ImageView 的效果是一样的,如果使用 SimpleTarget 就需要考虑到 View 的尺寸问题了。

在使用 Target 的时候,还有一点需要额外注意的,如果不是和页面绑定的图片资源,可以使用 ApplicationContext() ,避免当前页面被销毁之后,加载的请求也被停止了。

五、小结

本文最开始只是想要做一个适合初学者快速上手的 Glide 使用手册,但是越写越长,读到这里相信你也能有所收获,之后如果觉得有写概念也需要初学者了解,会继续补充。