Glide4 使用教程
Glide4 使用教程
一、前言
记得我刚开始学习 Android 那会,还不懂三方框架,比如 Picasso、Glide、Fresco 等图片加载框架,为了加载一个网络图片老费劲了(也可能是萌新,懂的少),首先要通过网络请求得到一个输入流,然后再通过 BitmapFactory.decodeStream() 方法获取 Bitmap 对象,最后在设置给 ImageView。
时不时还会出现点 OOM 异常给点惊喜,也是很崩溃了==。
后来慢慢了解了三方框架之后,知道了 Google 出品的 Glide。
看下官网的介绍:
Glide 是一个快速高效的 Android 图片加载库,注重于平滑的滚动。Glide 提供了易用的 API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。
Glide 支持拉取,解码和展示视频快照,图片,和GIF动画。Glide的Api是如此的灵活,开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。虽然Glide 的主要目标是让任何形式的图片列表的滚动尽可能地变得更快、更平滑,但实际上,Glide几乎能满足你对远程图片的拉取/缩放/显示的一切需求
Glide 可以很方便的加载图片,方便到只需要一行代码:
Glide.with(mContext).load(url).into(mImageView);
至此之后,我就一直在使用 Glide ,虽然在项目中使用过很多 Glide 提供的特性来方便开发,但是过一段时间容易忘记。一直打算写篇文章介绍下 Glide。
正好最近我把 Glide 的版本升级到了 4.0,就决定来写一篇文章来讲下 Glide 的使用,以及在项目中遇到的问题的解决办法。
二、Glide 基本使用
Glide 的 Github 地址:glide
如果你想使用,只需要下面的代码就可以很方便的引入到项目中:
implementation 'com.github.bumptech.glide:glide:4.7.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.7.0'
对了,Glide 是需要使用网络的,别忘了添加网络权限
<uses-permission android:name="android.permission.CAMERA" />
1、简单用法
比如加载一个地址为 url 的图片到 mImageView 中:
Glide.with(mContext).load(url).into(mImageView);
2、占位图
2.1 加载占位图
如果你要加载的图片比较大,需要的时间比较长,那么你可以设置一个默认的图片 default_picture,在真正的图片没加载的时候显示,你可以这样做:
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.a);
Glide.with(this).load(url).apply(options).into(mImageView);
2.2 错误占位图
如果你不确定图片能不能成功,想在加载错误的时候显示特定的图片,来表示加载失败,你可以这样做:
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.a)
.error(R.drawable.d);
Glide.with(this).load(url).apply(options).into(mImageView);
此外,从 Glide 4.3.0 开始,你现在可以使用 error API 来指定一个 RequestBuilder,以在主请求失败时开始一次新的加载,
String errorUrl ="失败要加载的地址";
Glide.with(this)
.load(url)
.error(Glide.with(this).load(errorUrl))
.into(mImageView);
这样的话,如果我们加载 url 失败,则会去重新加载 errorUrl。
2.3 null 占位图(后备回调符)
正常情况下,我们的 load() 方法里面的 url 应该不是 null 的,但是如果有可能为 null 的情况,你可以通过设置 fallback() 方法来显示 url 为 null 的情况,代码如下:
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.a)
.error(R.drawable.d)
.fallback(R.drawable.e)
.override(com.bumptech.glide.request.target.Target.SIZE_ORIGINAL);
Glide.with(this).load(url).apply(options).into(mImageView);
图片显示流程为
1. 正在加载 url 的时候,页面上显示图片 a
2. 加载成功显示 url 指向的图片
3. 加载失败显示 图片 e
4. 如果 url 为 null(一定得是 null ),则显示 图片 e
3、加载指定大小的图片
如果你要加载的图片尺寸是 1080*1920 的,但是你要显示的 ImageView 的大小却是 100 * 100的,这个时候,我们是不用做什么操作的,因为 Glide 会自动的根据 ImageView 的大小来决定加载图片的大小
但是如果你的 ImageView 是 100 * 100 的,而你却要加载的图片大小为 88 * 88,那么你就可以这样写来指定加载的尺寸:
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.a)
.error(R.drawable.d)
.override(88, 88);
Glide.with(this).load(url).apply(options).into(mImageView);
这里需要注意的是,虽然设置了要加载图片的大小,但是设置的 placeholder 和 error 的尺寸是不会变的,还是通过 Glide 根据我们的 ImageView 自动计算的。
当然,你可能说,我不想让 Glide 帮我计算并压缩要加载的图片,我就要加载原始图片大小,当然也是可以的,你可以这样写:
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.a)
.error(R.drawable.d)
.override(com.bumptech.glide.request.target.Target.SIZE_ORIGINAL);
Glide.with(this).load(url).apply(options).into(mImageView);
不过我不建议这样做,因为比如你的原始图片大小 1024 * 1024 的,而你的 ImageView 是 128 * 128 的,那么两种加载方式占用的内存可能会相差几十倍。
4、加载不同格式 Gif、Bitmap、Drawable、File
在 Glide4.0 中有一个 RequestBuilders 的泛型类,用于指定加载资源的格式,可以通过下面四种方法指定,得到不同的 RequestBuilders 对象:
- asDrawable() 得到 RequestBuilders
- asGif() 得到 RequestBuilders
- asBitmap() 得到 RequestBuilders
- asFile() 得到 RequestBuilders
默认情况下,如果我们不指定,则是得到一个 RequestBuilders 对象。
比如我们加载 Gif 时可以使用:
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.a)
.error(R.drawable.d)
.override(com.bumptech.glide.request.target.Target.SIZE_ORIGINAL);
// 加载 gif
Glide.with(this).asGif().load(url).apply(options).into(mImageView);
// 加载 bitmap
Glide.with(this).asBitmap().load(url).apply(options).into(mImageView);
// 加载 drawable
Glide.with(this).asDrawable().load(url).apply(options).into(mImageView);
asFile() 和后面要讲的有关系,后面再讲。
5、缩略图的使用
如果在开发中,你要加载的图片很大,并且你要加载的图片有高分辨率版本和低分辨率版本,我们知道,高分辨路意味着图片较大,加载耗时,所以 Glide 给我们提供了 thumbnail 方法来加载缩略图。
thumbnail 方法的参数主要有以下两种:
- RequestBuilder thumbnailRequest
- float sizeMultiplier
来看下第一种 参数为 RequestBuilder 的:
String highQualityImageUrl = "...";
String lowQualityImageUrl = "...";
Glide.with(this)
.load(highQualityImageUrl)
.thumbnail(Glide.with(this)
.load(lowQualityImageUrl))
.into(mImageView);
这种情况下会先加载并显示 lowQualityImageUrl 指向的图片,等到 highQualityImageUrl 指向的图片加载完成之后,则显示 highQualityImageUrl 指向的图片。
第二种,参数为 float 的:
看下源码:
public RequestBuilder<TranscodeType> thumbnail(float sizeMultiplier) {
if (sizeMultiplier < 0f || sizeMultiplier > 1f) {
throw new IllegalArgumentException("sizeMultiplier must be between 0 and 1");
}
this.thumbSizeMultiplier = sizeMultiplier;
return this;
}
看到我们只能传入 0 到 1 之间的 float 值。用法如下
Glide.with(this)
.load(highQualityImageUrl)
.thumbnail(0.5f)
.into(mImageView);
这种情况下会直接 highQualityImageUrl 指向的图片的一般分辨率的图片,等到 highQualityImageUrl 指向的图片完全加载之后,再显示 highQualityImageUrl 指向的完整图片。
6、Glide 预加载、缓存到硬盘、以及加载监听
6.1 换一种方式加载图片
正常情况下我们都是用下面这行代码加载图片的:
Glide.with(mContext).load(url).into(mImageView);
我们最后的 into() 方法传入了一个 ImageView,实际上这只是 Glide 为我们提供的一个封装,封装成一个 Target 或者 Target 的子类,看下 Glide 的定义:
看下 into(mImageView) 的实现:
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
// 。。。省略部分代码
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
前面省去的代码是对一些对象的拼装,最终调用的 into 方法:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options) {
而这里的 TranscodeType 指的就是 设置的 asBitmap()、asDrawable()等之后设置的类型。
实际上一个常规的加载图片的代码可以这样写:
//Target<R> 是一个接口,所以使用其实现类 SimpleTarget 来实现
Glide.with(this).load(url).into(new SimpleTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
mImageView.setImageDrawable(resource);
}
});
当然 Target 还有其他的实现类,有兴趣的可以去研究下。
6.2 预加载图片到缓存
如果说我们有两个页面A(当前页面) 和 B(跳转页面),在 B 页面中要使用 Glide 显示一个很大的图片,我们可以在 A 页面的时候就可以先把 B 页面中要加载的图片缓存下来,等到 B 页面的时候,就可以直接从换从中读取了。
示例代码:
下面的 url 均指向相同的地址
在页面 A 中:
Glide.with(this)
.load(url)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, com.bumptech.glide.request.target.Target<Drawable> target, boolean isFirstResource) {
Toast.makeText(GlideActivity.this, "加载失败,下个页面从网络取", Toast.LENGTH_SHORT).show();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, com.bumptech.glide.request.target.Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
Toast.makeText(GlideActivity.this, "加载成功,下个页面从缓存中取", Toast.LENGTH_SHORT).show();
return false;
}
}).preload();
在页面 B 中:
Glide.with(this).load(url).into(mImageView);
这样当在 A 中提示 “加载成功,下个页面从缓存中取” 的时候,跳转到页面 B ,就可以很快的显示图片了。
6.3 下载图片到指定地址
除了上面的预加载之外,我们也可以先把图片下载到硬盘上,得到一个 File 文件,这个时候要用到 submit() 方法。
源码如下:
@NonNull
/**
* Returns a future that can be used to do a blocking get on a background thread.
*
* <p>This method defaults to {@link Target#SIZE_ORIGINAL} for the width and the height. However,
* since the width and height will be overridden by values passed to {@link
* RequestOptions#override(int, int)}, this method can be used whenever {@link RequestOptions}
* with override values are applied, or whenever you want to retrieve the image in its original
* size.
*
* @see #submit(int, int)
* @see #into(Target)
*/
@NonNull
public FutureTarget<TranscodeType> submit() {
return submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}
从源码上的注释来看,这个方法是要会阻塞线程的,所以要在子线程中执行
当调用了submit()方法后会立即返回一个FutureTarget对象,然后Glide会在后台开始下载图片文件。接下来我们调用FutureTarget的get()方法就可以去获取下载好的图片文件了,如果此时图片还没有下载完,那么get()方法就会阻塞住,一直等到图片下载完成才会有值返回
示例代码:
我们使用 AsyncTask 来实现:
class MyAsyncTask extends AsyncTask<String, Void, File> {
@Override
protected File doInBackground(String... strings) {
FutureTarget<File> target = Glide.with(GlideSecondActivity.this)
.asFile()
.load(strings[0])
.submit();
try {
return target.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
Toast.makeText(GlideSecondActivity.this, "Glide开始下载图片", Toast.LENGTH_SHORT).show();
}
@Override
protected void onPostExecute(File file) {
super.onPostExecute(file);
Log.d(TAG, "图片下载完成,地址为" + file.getAbsolutePath());
Toast.makeText(GlideSecondActivity.this, "图片下载完成,地址为" + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
try {
FileInputStream inputStream = new FileInputStream(file);
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
mImageView.setImageBitmap(bitmap);
inputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
//然后在主线程中调用:
new MyAsyncTask().execute(url);
这里就用到了我们前面提到的 asFile() 方法了。指定返回一个 File 类型。
可以看到缓存的地址为:
图片下载完成,地址为/data/user/0/com.sean.demo/cache/image_manager_disk_cache/12fd3b7072800c1ef45fce86fb47393e7bfc37b3f19b2b3694132ba9105359a6.0
6.4 加载监听
其实加载监听,上面已经使用过了,就是设置 .listener()方法,传入 Listener 对象。
比如:
Glide.with(this)
.load(url)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, com.bumptech.glide.request.target.Target<Drawable> target, boolean isFirstResource) {
Toast.makeText(GlideActivity.this, "加载失败,下个页面从网络取", Toast.LENGTH_SHORT).show();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, com.bumptech.glide.request.target.Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
Toast.makeText(GlideActivity.this, "加载成功,下个页面从缓存中取", Toast.LENGTH_SHORT).show();
return false;
}
}).preload();
- onLoadFailed 方法就是加载失败
- onResourceReady 就是加载成功
onResourceReady() 方法和 onLoadFailed() 方法都有一个布尔值的返回值,返回 false 就表示这个事件没有被处理,还会继续向下传递,返回 true 就表示这个事件已经被处理掉了,从而不会再继续向下传递。举个简单点的例子,如果我们在 RequestListener 的 onResourceReady() 方法中返回了 true,那么就不会再回调 Target的onResourceReady() 方法了。
7、图片变换
7.1 单次变换
在Glide中,Transformations 可以获取资源并修改它,然后返回被修改后的资源。通常变换操作是用来完成剪裁或对位图应用过滤器,但它也可以用于转换GIF动画,甚至自定义的资源类型。
Glide 内置了几种变换,比如 :
- CenterCrop
- FitCenter
- CircleCrop
比如我们显示圆形图片,我们可以这样做:
RequestOptions options = new RequestOptions().circleCrop();
Glide.with(this).load(url).apply(options).into(mImageView);
显示效果:
你也可以这样写:
Glide.with(this).load(url).apply(com.bumptech.glide.request.RequestOptions.circleCropTransform()).into(mImageView);
和上面的效果是一样的。
需要注意的的,如果你按照下面的写法:
Glide.with(this)
.load(url)
.apply(RequestOptions.centerCropTransform())
.apply(RequestOptions.circleCropTransform())
.into(mImageView);
Glide 只会执行后面的变换,业技术 RequestOptions.circleCropTransform 变换。
如果你想一次加载中变换多次,那么你可以这样使用 MultiTransformation。
7.2 多次变换
以下代码可以实现多次变换:
RequestOptions options = new RequestOptions()
.transform(new MultiTransformation<Bitmap>(new CenterCrop(),new CircleCrop()));
Glide.with(this)
.load(url)
.apply(options)
.into(mImageView);
除了上面介绍的两种变换,你可以自己定义变换,这里推荐一个开源库,就是做 Glide 变换的,有兴趣的可以去看下:
GitHub地址
8、使用 Generated API
GlideModule
如果你不喜欢在使用 Glide4 的时候创建 RequestBuilder、RequestOptions 等对象,而是喜欢 Glide4 之前的链式调用,那么你可以使用 Generated API。
使用 Generated API 必须要先引入以下依赖:
annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1'
这是 Glide 为我们提供的注解处理器,通过这个注解处理器,在 Application 模块中可使用该流式 API 一次性调用到 RequestBuilder, RequestOptions 和集成库中所有的选项,为我们提供类似 Glide3 那样的流式 API。
Generated API 模式的设计出于以下两个目的:
- 集成库可以为 Generated API 扩展自定义选项。
- 在 Application 模块中可将常用的选项组打包成一个选项在 Generated API 中使用
下面来简单使用下:
首先创建 MyAppGlideModule 继承于 AppGlideModule,并给类添加 @GlideModule 注解:
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
}
然后执行 Rebuild Project操作。
就这样,很简单的你就能使用类似 Glide4 之前的链式调用了。比如:
GlideApp.with(this)
.load(url)
.placeholder(R.drawable.a)
.error(R.drawable.b)
.into(mImageView);
对比下 Glide4 的用法:
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.a)
.error(R.drawable.b);
Glide.with(this)
.load(url)
.apply(options)
.into(mImageView);
使用 Generated API 可以使我们不用去创建 RequestOptions 就可以直接使用 placeholder、error 等方法。
使用GlideExtension
除此之外,Generated API 还支持定制自己的 API 来使用。
比如我项目中的所有使用 Glide 加载动画是不使用动画的,我们就要使用 dontAnimate() 方法,同时还要设置默认的加载错误图片,可以这样写:
新建一个 MyGlideExtension 类,加上 @GlideExtension 注解:
@GlideExtension
public class MyGlideExtension {
private MyGlideExtension() {
}
/**
* 设置默认error 图片,并禁止动画
*
* @param options
*/
@GlideOption
public static void defaultError(RequestOptions options) {
options.placeholder(R.drawable.c)
.dontAnimate();
}
}
然后执行 Rebuild Project操作。
这样用的时候就可以这样使用:
GlideApp.with(this)
.load(url)
.defaultError()
.into(mImageView);
当然, 如果你有其他的需求,完全可以在被 @GlideOption 注解的方法里面加上。赶紧去动手定制你想用的 API 吧。
GlideType
被 @GlideType 注解的方法允许你添加对新的资源类型的支持,包括指定默认选项
这个在日常开发中一般是用不上的,如果有需求可以去了解下相关内容。
三、遇到的问题以及解决方法
1、Glide 和 RecyclerView 结合使用出现卡顿
我们经常会遇到在一个 RecyclerView 中加载图片的情况,正常情况下使用的话,在 RecyclerView 滑动的时候,item 回去加载图片,可能 会造成卡顿,那么怎么让 RecyclerView 的滑动更加顺滑呢?
思路基本就是:
在 RecyclerView 滑动的时候,不加载图片,等到 RecyclerView 滑动停止的时候再去执行加载图片的操作。
怎么实现呢?
其实很简单, 我们只要监听 RecyclerView 的滑动就可以了。
// 声明RecyclerView.OnScrollListener
private RecyclerView.OnScrollListener mOnScrollListener;
// RecyclerView 是否在滑动
private boolean sIsScrolling;
/**
* SCROLL_STATE_DRAGGING 正在滚动
* SCROLL_STATE_SETTLING 手指做了抛的动作(手指离开屏幕前,用力滑了一下)
* SCROLL_STATE_IDLE 停止滚动
*/
mOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING) {
sIsScrolling = true;
Glide.with(RecyclerDemoActivity.this).pauseRequests();
} else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (sIsScrolling) {
Glide.with(RecyclerDemoActivity.this).resumeRequests();
}
sIsScrolling = false;
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
};
recyclerDemo.addOnScrollListener(mOnScrollListener);
@Override
protected void onPause() {
super.onPause();
recyclerDemo.removeOnScrollListener(mOnScrollListener);
}
首先定义对 RecyclerView 滑动的监听 mOnScrollListener,以及 RecyclerView 是否在滑动的变量。
然后初始化 mOnScrollListener,并给 RecyclerView 添加此监听。
然后在 Activity 的 onPause 方法中移除对 RecyclerView 滑动监听。
这样,在 RecyclerView 滑动的时候,Glide 就不会去加载图片,只有当滑动完全停止的时候,Glide 才回去加载 item 上的图片。
2、对于指向同一地址的图片显示
假如你要显示的图片,一直指向一个地址,比如 http://www.smartsean.cn/image1
,但是这个地址里面的图片可能会有变化,那么,你可能需要禁用 Glide 为我们提供的缓存功能
RequestOptions options = new RequestOptions()
//禁用磁盘缓存
.diskCacheStrategy(DiskCacheStrategy.NONE)
//禁用内存缓存
.skipMemoryCache(true);
3、Glide的OOM
如果设置 ImageView 的 ScaleType 是 fitxy ,Glide 会默认按照图片实际大小加载。而其他的模式按照的 ImageView 的大小。
如果非要设置 fitxy,那么使用
Glide.with(context).load().centerCrop().into();
或者使用
Glide.with(context).load().fitCenter().into()
后面如果有一些问题,会持续更新
四、总结
从上面的文章可以看出, Glide4 提供的功能还是很强大的,帮助我们做了很多事情,但是我们使用的时候,也不能掉以轻心。
这篇算是我对使用 Glide4 的总结,后续如果有内容会持续更新。
推荐阅读
-
使用IntelliJ IDEA 2017.2.5 x64中的Spring Initializr插件快速创建Spring Boot/Cloud工程(图解)
-
Java使用for循环解决经典的鸡兔同笼问题示例
-
MySQL的指定范围随机数函数rand()的使用技巧
-
CentOS 7.0下使用yum安装mysql的方法详解
-
mysql 5.7.14 下载安装、配置与使用详细教程
-
在windows10上安装mysql详细图文教程
-
Spring MVC温故而知新系列教程之请求映射RequestMapping注解
-
Mysql的基础使用之MariaDB安装方法详解
-
ES6中字符串的使用方法扩展
-
浅谈react-router@4.0 使用方法和源码分析