Glide的强大和灵活相信不需要多介绍了
本文使用Glide版本为4.8.0,因为使用的Java语言进行开发,涉及到使用Kotlin的部分还请参考官方文档
SDK要求
- 最小SDK版本需要使用
API 14
(或者更高版本) -
Complie SDK Version
需要使用API 27
(或者更高版本) -
Glide
使用的SupportLibrary
版本是27,如果需要不同的SupportLibrary
版本可以用exclude
将Glide
的SupportLibrary
从依赖中去掉,具体的在集成时说明
一、集成
在项目的build.gradle
文件中添加google()
仓库
repositories {
google()
//or maven { url 'https://maven.google.com' }
jcenter()
}
在要使用的Glide
的module
中添加以下代码
implementation('com.github.bumptech.glide:glide:4.8.0')
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
//如果使用了在Kotlin中使用了Glide注解,需要引入kapt依赖代替annotationProcessor依赖
//kapt 'com.github.bumptech.glide:compiler:4.8.0'
若使用了不是27的SupportLibrary
版本,使用以下代码
implementation('com.github.bumptech.glide:glide:4.8.0') {
exclude group: "com.android.support"
}
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
使用了不同版本的SupportLibrary
可能导致一些运行时异常,
java.lang.NoSuchMethodError: No static method getFont(Landroid/content/Context;ILandroid/util/TypedValue;ILandroid/widget/TextView;)Landroid/graphics/Typeface; in class Landroid/support/v4/content/res/ResourcesCompat; or its super classes (declaration of 'android.support.v4.content.res.ResourcesCompat'
at android.support.v7.widget.TintTypedArray.getFont(TintTypedArray.java:119)
尽量避免使用@aar
,如果需要这么做,需要添加transitive=true
确保有所需要的类。
dependencies {
implementation ("com.github.bumptech.glide:glide:4.8.0@aar") {
transitive = true
}
}
@aar
是Gradle
的限制符,默认会去除使用到得依赖。如果不添加transitive=true
的话将会导致运行时异常。
java.lang.NoClassDefFoundError: com.bumptech.glide.load.resource.gif.GifBitmapProvider
at com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder.<init>(ByteBufferGifDecoder.java:68)
at com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder.<init>(ByteBufferGifDecoder.java:54)
at com.bumptech.glide.Glide.<init>(Glide.java:327)
at com.bumptech.glide.GlideBuilder.build(GlideBuilder.java:445)
at com.bumptech.glide.Glide.initializeGlide(Glide.java:257)
at com.bumptech.glide.Glide.initializeGlide(Glide.java:212)
at com.bumptech.glide.Glide.checkAndInitializeGlide(Glide.java:176)
at com.bumptech.glide.Glide.get(Glide.java:160)
at com.bumptech.glide.Glide.getRetriever(Glide.java:612)
at com.bumptech.glide.Glide.with(Glide.java:684)
权限
这个不需要多说,如果加载网络图片必然需要网络权限,这个还是根据具体的使用情况而定。
<uses-permission android:name="android.permission.INTERNET" />
Glide
添加了链接监听(Connectivity Monitoring),通过网络加载图片时,Glide可以监听链接状态并在重新链接到网络时重启之前失败的请求。使用链接监听我们需要添加ACCESS_NETWORK_STATE
权限,Glide将自动监听连接状态,不需要额外的改动。
如果需要使用ExternalPreferredCacheDiskCacheFactory
将Glide的缓存存放到SD卡上,还需要添加READ_EXTERNAL_STORAGE
和WRITE_EXTERNAL_STORAGE
权限。
混淆
如果使用了混淆,需要添加以下的代码到混淆的配置文件中
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
如果使用的target API低于27的话还需要添加
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
VideoDecoder
使用API 27
的一些借口,可能导致混淆发出警告。
如果使用了DexGuard
,可能还需要添加
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
二、简单使用
V4的基本用法还是没变,一句简单地链式调用就能讲图片加载到ImageView
上了
Glide.with(context)
.load(url)
.into(imageView);
这里context
就是一个Context
对象,而url
是一个网络图片链接,imageView
则是需要显示图片的ImageView
。V4还可以手动取消加载,例如:
Glide.with(context).clear(imageView);
Glide.with()
中传入的Activity
或Fragment
销毁时,Glide
会自动取消加载并回收资源
这里会提出clear
方法,在RecyclerView
中复用View
来加载图片,每次需要调用Glide
重新进行加载操作或者使用clear
方法停止之前的请求,避免图片显示错乱的问题。
可以看到with
方法还是很强大,支持Context
,Activity
,Fragment
,View
等,只是将原本支持传入android.app.Fragment
的方法标记为过期了。
load
方法也提供了强大的支持,Uri、文件、btye[]、网络连接、资源id、bitmap、drawable都可以加载。
Generated API(生成API)
Glide V4使用注解处理器生成一个流式API,用于RequestBuilder
,RequestOptions
等相关的所有选项。Glide的文档中有说明,其目的一是为了更好地扩展自定义选项,其二是为了方便打包常用选项组。
对
RequestOptions
的设置,Glide V4还提供了apply
方法设置单次请求的选项,以及applyDefaultRequestOptions
设置默认请求选项。
从Glide V3到Glide V4的升级,生成API是一个很大的改变,接下来详细说明。
生成API目前只能在Application
模块中使用,无法同时在各个Library
和Application
中定义各自的生成API,确保了调用API时请求的选项是一致的。官方也指出,在将来的版本中可能解除此限制。
使用Generated API仅仅需要两步,第一步,在build.gradle
的依赖中添加annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
上面其实已经给出;第二步,我们需要创建一个class
继承AppGlideModule
,并为该类添加@GlideModule
注解。
package com.mrtrying.glidev4_example;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
GlideModule
public final class ExampleAppGlideModule extends AppGlideModule {}
AppGlideModule
虽然是抽象类,却可以不用重写任何方法。但必须使用@GlideModule
注解标记该类,否则没法顺利的生成GlideApp
的API
。
PS:第一次添加AppGlideModule
或者对AppGlideModule
做了某些修改时,我们需要重新构建项目重新生成API。如果AndroidStudio没法自动完成构建,可以使用Build—->Rebuild Project
手动重新构建,必要时可能需要手动删除module
的build
目录再重新构建项目。
生成API的默认名为GlideApp
,其包名与所在的module
的包名相同,而基本用法也于之前相同,只是将Glide
替换成了GlideApp
,就可以使用了
GlideApp.with(this).load(url).into(imageView);
Glide生成API还有
GlideExtension
、GlideOption
、GlideType
,暂时只对AppGlideModule
进行说明,其他的涉及到较深度的使用,放在后面说明。
占位符
Glide提供了三种不同类型的占位符:
- placeholer(占位符)
- error(错误符)
- fallback(后备回调符)
GlideApp.with(this)
.load(url)
.placeholder(R.mipmap.placeholder)
.error(R.mipmap.error)
.fallback(R.mipmap.fallback)
.into(imageView);
先说API的参数,placeholder
、error
、fallback
相同可以传入资源id或者Drawable对象。
接下来说说这三个占位符的调用时机,用过V3的同学一定知道placeholer
占位符是当资源正在请求是被展示的图片或者资源文件,当请求成功时占位符会被替换成请求到得资源;error
则是在请求永久性失败是展示;fallback
是当请求的url(或者model)为null
时展示,此占位符的目的是设置null
是否是可接收的正常情况,而Glide将null
作为错误处理。
三个占位符对应了三种时机,其中还有一些状态,无可避免的会出现一些复杂的情况需要说明:
- 设置了
placeholder
之后,如果请求失败,但是没有设置error
,那么占位符将继续显示;而请求的url/model为null也会导致请求失败,一样的,没有设置error
和fallback
的话,占位符也将继续显示 - 如果请求的url/model为null导致请求失败,
fallback
会优先于error
调用,也就是说url为null时设置了error
而没有设置fallback
将继续显示error
;反之,设置了fallback
就会显示fallback
占位符这个功能确实很棒,有朋友不经要问了,如果我想加载网络图片怎么办?这个就十分抱歉了,Glide并不提供这样的功能,只能支持资源文件或者是Drawable
对象。当然,你也可以提前下载要网络图片在以Drawable
形式加载,只要这样的效率你可以接收(个人觉得没有什么必要)。
官方文档在占位符最后的FAQ中有说明,占位符是在主线程中,从Android Resource加载的,而且Glide的Transformation
不会被应用到占位符上。这都是希望在使用占位符时能尽量少得占用系统资源来考虑的,所以Glide并没有提供他们认为不太合理的API。
关于图片的变换,比较常用到得是圆角和模糊,圆角的处理的话比较推荐RCLayout,虽然在背景的处理上会有一点锯齿,不过还是很不错的一个库;而模糊的话推荐使用android自带的
RenderScript
,相关方法在API 17
以上提供,也提供了相关的support
包。
transformation(变换)
Glide提供了transformation
得功能,在获取到请求的图片之后,能对图片进行一些处理,例如:裁剪、模糊等;而transformation
的强大在于可以自定义,这样一来transformation
不仅能处理bitmap
,同样可以用于处理GIF动画,还有自定义资源类型。
Glide在API上提供的了5个相关的方法可以直接使用
- circleCrop()
- centerCrop()
- centerInside()
- fitCenter()
- optionalFitCenter()
optionalFitCenter()
的具体作用没有太清楚,效果与fitCenter()
相同,有知道的大佬请告知
使用方式基本和占位符一致,在load()
方法后调用相关方法就能使用相应的transformation
效果了
GlideApp.with(this)
.load(url)
.centerCrop()
.into(imageView);
这是在生成API中的使用,在Glide普通的API也是可以使用的,不过没有直接可以调用的方法,需要通过RequestOptions
来设置
RequestOptions options = new RequestOptions().centerCrop();
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
除了可以自己创建以外,Glide提供了静态获取RequestOptions
方法,可以直接使用
Glide.with(this)
.load(url)
.apply(RequestOptions.centerCropTransform())
.into(imageView);
针对
ImageView
可能自身也会设置scaleType
的情况,Glide在部分情况会自动应用FitCenter
或CenterCrop
,如果scaleType
是CENTER_CROP
,Glide
将会自动应用CenterCrop
变换。如果scaleType
为FIT_CENTER
或CENTER_INSIDE
,Glide
会自动使用FitCenter
变换。
当然,设置了其他的变换也可以将其覆盖;或者使用dontTransform()
方法就不会在执行任何变换。
这里需要注意的是,Glide内置的这几种transformation
,即使在使用多种变换,也只有最后一个transformation
会生效。这个不仅仅是对Glide内置的transformation
也包括transform()
设置的transformation
,都会替换掉之前的transformation
。
如果需要支持多种变换,需要使用transform()
设置MultiTransformation
类对象传入,MultiTransformation
的构造器可以接收可变参数或者Transformation
的集合;或者在transforms()
方法中设置多个(不要以为是一个方法,这是transforms()
,结尾有s
的)而这些个变化的应用顺序就是传入参数的顺序。
GlideApp.with(this)
.load(url)
.transform(new MultiTransformation<>(new FitCenter(),new RoundedCorners(3)))
.into(imageView);
//或者
GlideApp.with(this)
.load(url)
.transforms(new FitCenter(),new RoundedCorners(3))
.into(imageView);
由于
Transformation
是没有状态的,我们可以再多个加载中复用同一个Transformation
对象。
自定义transformation
为了方便使用,我们希望一些特殊的变换也能像上面一样的使用,这种情况可以通过实现Transformation
来处理。
这里直接以Glide内置实现的RoundedCorners
为例
public final class RoundedCorners extends BitmapTransformation {
private static final String ID = "com.bumptech.glide.load.resource.bitmap.RoundedCorners";
private static final byte[] ID_BYTES = ID.getBytes(CHARSET);
private final int roundingRadius;
/**
* @param roundingRadius the corner radius (in device-specific pixels).
* @throws IllegalArgumentException if rounding radius is 0 or less.
*/
public RoundedCorners(int roundingRadius) {
Preconditions.checkArgument(roundingRadius > 0, "roundingRadius must be greater than 0.");
this.roundingRadius = roundingRadius;
}
@Override
protected Bitmap transform(
@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
return TransformationUtils.roundedCorners(pool, toTransform, roundingRadius);
}
@Override
public boolean equals(Object o) {
if (o instanceof RoundedCorners) {
RoundedCorners other = (RoundedCorners) o;
return roundingRadius == other.roundingRadius;
}
return false;
}
@Override
public int hashCode() {
return Util.hashCode(ID.hashCode(),
Util.hashCode(roundingRadius));
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(ID_BYTES);
byte[] radiusData = ByteBuffer.allocate(4).putInt(roundingRadius).array();
messageDigest.update(radiusData);
}
}
可以看到RoundedCorners
是继承自BitmapTransformation
,BitmapTransformation
已经帮我们处理了部分基础逻辑,比如提取和回收原始的Bitmap
,而我们处理好Bitmap
的变换就可以了。
除了自身的构造函数意外还重写了4个方法,其中transform
方法就是进行变换处理的方法,变换完成之后返回变换完成的Bitmap
。其余的三个方法,updateDiskCacheKey
是必须实现的,而为例保证内存和磁盘缓存正常,equals()
和hashCode()
也是必须重写的。
RoundedCorners
使用完整报名路径的限定名来作为一个 ID,它可以构成 hashCode()
的基础,并可用于更新 updateDiskCacheKey()
传入的 MessageDigest
。如果你的 Transformation 需要参数而且它会影响到 Bitmap 被变换的方式,它们也必须被包含到这三个方法中,就像RoundedCorners
一样。原来的ID保留,但roundingRadius
也包含到了这个三个方法中,updateDiskCacheKey
方法还演示了你可以如何使用 ByteBuffer
来包含基本参数到你的 updateDiskCacheKey
实现中。
官方文档着重指出必须要重写equals()
和hashCode()
方法,虽然不重写不会出现编译问题,但是这不代表能正常工作。
三、进阶
Target
或许在使用into()
时有些印象,into()
方法可以直接传入ImageView
也可以使用Target
。其实跟踪一下Glide的源码就会发现其实在传入ImageView
时,最终也在ImageViewTargetFactory
的包装下返回了ImageViewTarget
,而ViewTarget
的最上层的父类就是Target
。
into()
方法不仅用于启动每一个请求,同时也制定了接收请求结果的Target
。
Target<Drawable> target = GlideApp.with(this)
.load(url)
.centerCrop()
.into(new Target<Drawable>() {
//Target的方法太多,这里代码省略...
});
如果重用同一个Target
对加载一个新的请求,那么之前的的请求都会被取消并且释放资源。我们也可以使用clear()
方法对不需要重新加载的请求进行相关资源的释放。
GlideApp.with(this).clear(target);
上面的情况中直接使用ImageVIew
也是可以的。因为ViewTarget
使用了setTag()
和getTag()
存储了Request
,所以可以直接从View
的tag
取回之前一次加载的信息,也是是ImageVIew
的默认tag
被占用的原因。
也就是说,在使用ImageVIew的情况中,即使使用同一个ImageView重新加载也是可以释放之前的请求和资源的
Glide.with(this)
.load(url)
.into(imageView);
//加载新连接
Glide.with(this)
.load(newUrl)
.into(imageView);
只要继承ViewTarget
或者重写setRequest()
和getRequest()
并实现取回上一次加载的信息,重用的机制就可以得以保证。
Transitions(过渡)
Glide V3和V4不同,不会默认应用交叉淡入或任何其他的过渡效果,每个请求需要手动应用过渡。Glide提供了很多过渡动画,我们可以手动应用于每一个请求上;内置过渡的运行方式是一致的,会根据加载图像不同的情况来决定是否执行过渡。例如:如果Glide从内存缓存中加载出来,Glide的内置过渡将不会执行;而加载磁盘缓存、本地文件或者远程连接时都会执行Glide的内置过渡。
可以通过transition()
方法设置TransitionOptions
Glide.with(this)
.load(url)
.transition(DrawableTransitionOptions.withCrossFade())
.into(imageView);
TransitionOptions
用于给一个特定的请求指定过渡,而不同的资源类型能决定使用什么类型的过渡选项。Bitmap 和 Drawable可以对应使用使用 BitmapTransitionOptions
或 DrawableTransitionOptions
来指定类型特定的过渡动画。对于 Bitmap
和 Drawable
之外的资源类型,可以使用 GenericTransitionOptions
。
如果需要自定义过渡动画,我们需要通过DrawableTransitionOptions.with()
生成我们自己的TransitionOptions
,而with()
需要出入一个TransitionFactory
对象。TransitionFactory
是一个接口我们需要实现一个这样的类
public class ExampleTransitionFactory implements TransitionFactory {
@Override
public Transition build(DataSource dataSource, boolean isFirstResource) {
return null;
}
}
通过DrawableTransitionOptions.with(new ExampleTransitionFactory())
我们就能调用transition()
方法来加载我们的定制的过渡动画。transition()
方法支持动画的资源id,Animator
和TransitionFactory
,可以通过这三种方式来实现动画部分。
最后就是执行transition()
来加载我们自定义的动画
GlideApp.with(this)
.load(url)
.transition(DrawableTransitionOptions.with(new ExampleTransitionFactory()).transition(R.anim.show))
.into(imageView);
这里需要提醒的是,动画对于性能的开销不用多说,比图片解码本身还要耗时,在列表的快速滑动的情况下可能造成加载缓慢。在列表中考虑是否使用动画,在一些希望图片尽快加载出来的时候也需要做此考虑。