Glide v4详解
简介下载配置使用简单使用高级用法RequestOptionsTransitionOptionsGenerated APIRequestBuilderConfiguration使用技巧Glide的图片变换TransformationsGlide的过渡动画TransitionsGlide的缓存管理Caching简介Glide项目于2012年12
简介
Glide项目于2012年12月21日由Google工程师Sam sjudd首次提交,到现在已经迭代了四个大的版本,受到了越来越多开发者的欢迎,Star数也已接近两万。Glide v4.x相对于之前比较稳定的Glide v3.7.0来说,有了很大的变化,一个比较大的改动就是Glide处理加载选项(如裁剪变换、占位符、缓存策略等)的方式。在之前的Glide v3中,加载选项由一系列复杂的builder处理,而Glide v4中这些已经被单一类型的builder所取代,且提供了一系列可供builder使用的选项对象(options objects)。
下载
在build.gradle中添加依赖:
compile 'com.github.bumptech.glide:glide:4.3.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.3.0'
Glide需要support库的支持,如果你的项目还没有依赖support库,还需要添加support-v4依赖:
compile 'com.android.support:support-v4:26.1.0'
配置
如果你使用了proguard混淆,可能需要添加如下混淆规则:
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
你还需要声明联网、磁盘读写权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
使用
简单使用
很多情况下,使用Glide加载图片只需要一行代码
Glide.with(fragment).load(myUrl).into(imageView);
取消加载也一样简单:
Glide.with(fragment).clear(imageView);
虽然清理资源加载是个很好的做法,不过很多情况下你没有必要这样做,因为Glide会根据你在with()
方法中传入的Activity
/Fragment
的生命周期决定资源的加载和清理。
高级用法
RequestOptions
Glide中很多选项设置都可以通过RequestOptions
类和apply()
方法完成,包括:
- 占位符(Placeholders)
- 图片变换(Transformations)
- 缓存策略(Caching Strategies)
- 编码质量、解码配置等组件选项(Component specific options)
RequestOptions requestOptions = new RequestOptions()
.placeholder(R.drawable.default_avatar)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.encodeQuality(90);
Glide.with(this)
.load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
.apply(requestOptions)
.into(imageView);
placeholder请求过程中显示,请求完成后被替换为请求到的资源。如果请求的资源是内存中的,placeholder可能就不会被显示。 error
如果请求失败且error Drawable没有设置,placeholder将继续被显示。
如果请求的url/model为空且error Drawable和fallback Drawable都没设置,placeholder将继续被显示。当请求永远失败时显示error Drawable。 fallback
当请求的url/model为空且fallback Drawable没设置时显示error Drawable当请求的url/model为空时显示fallback Drawable。 thumbnail请求
fallback Drawable主要目的是允许用户表明"空"是否被允许,因为Glide默认把空的url/model当做error处理。一个典型的场景就是未设置头像用户的avatar字段可能是null
,而显示的时候我们需要显示一个默认的用户头像,此时设置fallback是个很好地选择。缩略图请求像普通的全尺寸请求一样是一个完整的资源请求,如果缩略图请求在完整请求完成之前完成,就加载并展示缩略图请求请求到的资源。 error请求
缩略图由于比全尺寸图更小所以加载的更快,不过无法保证两个请求完成的顺序。
但是,如果缩略图请求在全尺寸请求之后完成,缩略图资源并不会替换全尺寸资源。从Glide v4.3.0开始,当你的主请求失败时,你可以通过 error()
API指定一个RequestBuilder
去开始一个新的请求。也就是说你可以指定一个备用请求,如果主请求成功完成那么你的errorRequestBuilder
将不会开始执行。如果你同时指定了thumbnail()
请求和error()
请求,一旦主请求失败error()
请求就开始执行,即使thumbnail()
请求成功了。
TransitionOptions
Glide可以通过TransitionOptions
设置请求完成后的显示行为,如:
- 淡入效果(View fade in)
- 交叉渐入渐出(Cross fade from the placeholder)
- 无过渡效果(No transition)
RequestOptions requestOptions = new RequestOptions()
.placeholder(R.drawable.default_avatar)
.circleCrop();
DrawableTransitionOptions transitionOptions = new DrawableTransitionOptions()
.crossFade();
Glide.with(this)
.load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
.apply(requestOptions)
.transition(transitionOptions)
.into(imageView);
如果不设置过渡效果,当图片加载完之后就会立刻替换之前的图片。为了避免这样突然的改变,让图片更平滑地展示,我们可以通过
TransitionOptions
添加过渡效果。不过Transitions只面对单个请求的上下文,所以你不能通过Transitions定义一个请求到另一个请求的过渡动画。
Glide v4默认不应用任何的过渡动画,如果需要必须手动为每个请求应用过渡动画。
Glide要加载的图片资源可能来自:
- Glide的内存缓存(memory cache)
- Glide的磁盘缓存(disk cache)
- 本地的File或Uri源
- 远程的Url或Uri源
如果加载的图片资源来自Glide的内存缓存,加载过程特别快,Glide的内置transitions将不会应用,其它情况Glide的内置transitions才会应用。
与RequestOptions
不同,TransitionOptions
与Glide要加载的资源类型直接关联(TranscodeType),也就是说,如果你请求一个Bitmap
,你需要使用BitmapTransitionOptions
而不是DrawableTransitionOptions
。
Generated API
可能你已经注意到了,像RequestOptions
、TransitionOptions
这些options虽然将选项设置清晰地隔离开来,但额外地去创建这些对象会影响流式编程的风格,我们更希望能够连贯地调用方法,类似这样:
GlideApp.with(fragment)
.load(myUrl)
.placeholder(placeholder)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.transition(withCrossFade())
.into(imageView);
所以,Glide v4使用annotation processor自动生成一个API,以便应用可以流式地使用包括RequestBuilder
, RequestOptions
在内的所有options。
Generated API有两个目的:
- 一些集成库可以继承Glide的API去自定义一些options
- 应用也可以继承Glide的API去添加一些绑定通用options的方法
要使用Generated API也很简单,只需要在应用中写一个AppGlideModule
的实现即可:
package com.example.myapp;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public final class MyAppGlideModule extends AppGlideModule {}
编译一下(Rebuild Project),apt就会在相同包名下生成包含GlideApp在内的一系列文件 ,使用GlideApp你就可以流式地使用所有options的方法了。
RequestBuilder
每次调用Glide.with()
你都会得到一个RequestBuilder
, 而RequestBuilder
就是Glide请求的主干,负责把你的options和请求的url/model结合起来并开始新的加载。
RequestBuilder
可以指定:
- 你想要加载的资源类型(Bitmap, Drawable等)
- 你想要加载的资源的url/model
- 你想要加载资源的view
- 你想要应用的
RequestOption
对象 - 你想要应用的
TransitionOption
对象 - 你想要加载的
thumbnail()
缩略图
RequestBuilder
与你想要加载的资源类型直接关联(TranscodeType),默认返回RequestBuilder<Drawable>
,你可以通过as...
方法改变: RequestBuilder<Bitmap> requestBuilder = Glide.with(fragment).asBitmap();
。RequestBuilder
可以被重用去开始多个加载:
RequestBuilder<Drawable> requestBuilder =
Glide.with(fragment)
.asDrawable()
.apply(requestOptions);
for (int i = 0; i < numViews; i++) {
ImageView view = viewGroup.getChildAt(i);
String url = urls.get(i);
requestBuilder.load(url).into(view);
}
Configuration
Glide v4中有两个类可以配置Glide: AppGlideModule
和LibraryGlideModule
,这两个类都可以注册ModelLoader
, ResourceDecoder
等额外组件,但只有AppGlideModule
允许更改包括缓存位置、大小在内的应用特定设置。
一个应用只能有一个AppGlideModule
实现(如果有多个编译时就会报错),所以libraries是不能提供AppGlideModule
的实现的。
为了让Glide更好地寻找AppGlideModule
和LibraryGlideModule
实现,它们的实现类必须用@GlideModule
注解,有了这个注解,Glide的annotation processor就可以在编译时找到所有的实现类了。
如果libraries想配置Glide,必须:
- 添加一到多个
LibraryGlideModule
实现 - 每个
LibraryGlideModule
都添加@GlideModule
注解 - 添加Glide的annotation processor依赖
@GlideModule
public final class OkHttpLibraryGlideModule extends LibraryGlideModule {
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
compile 'com.github.bumptech.glide:annotations:4.2.0'
如果应用想配置Glide,必须有且只有一个AppGlideModule
实现,且混淆时不能混淆这些实现。
Glide默认内存缓存使用LruResourceCache
(MemoryCache
接口的实现),内存缓存的大小由MemorySizeCalculator
决定,而MemorySizeCalculator
会根据设备的RAM大小和屏幕分辨率决定内存缓存的大小。 你可以自定义缓存实现或缓存大小,如
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
}
}
Glide默认磁盘缓存使用DiskLruCacheWrapper
,磁盘缓存默认大小是250 MB,默认路径是/data/data/your.application.package/cache/image_manager_disk_cache
,这个路径是应用内部缓存路径,如果你想要缓存到外部公共目录下,可以这样设置:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new ExternalDiskCacheFactory(context));
}
}
也可以指定内部/外部磁盘缓存大小、缓存路径:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
int diskCacheSizeBytes = 1024 * 1024 * 100; // 100 MB
builder.setDiskCache(
new InternalDiskCacheFactory(context, "cacheFolderName", diskCacheSizeBytes));
}
}
甚至可以自定义磁盘缓存的实现:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new DiskCache.Factory() {
@Override
public DiskCache build() {
return new YourAppCustomDiskCache();
}
});
}
}
利用AppGlideModule
,你可以为所有请求应用默认的RequestOptions
:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDefaultRequestOptions(
new RequestOptions()
.format(DecodeFormat.RGB_565)
.disallowHardwareBitmaps());
}
}
你也可以使用RequestManager
的applyDefaultRequestOptions
方法为某个Activity
/Fragment
应用默认的RequestOptions
:
Glide.with(fragment)
.applyDefaultRequestOptions(
new RequestOptions()
.format(DecodeFormat.RGB_565)
.disallowHardwareBitmaps());
虽然RequestManager
也有setDefaultRequestOptions
方法来完全替换之前默认RequestOptions
(通过AppGlideModule
或RequestManager
设置的),但是为了不影响一些默认选项,使用applyDefaultRequestOptions
更安全。
当加载bitmap出错(如OOM)时,Glide默认只会打印log,你可以使用GlideExecutor.UncaughtThrowableStrategy
指定出错时的策略:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
final UncaughtThrowableStrategy myUncaughtThrowableStrategy = new ...
builder.setDiskCacheExecutor(newDiskCacheExecutor(myUncaughtThrowableStrategy));
builder.setResizeExecutor(newSourceExecutor(myUncaughtThrowableStrategy));
}
}
为了方便调试,你可以修改Glide的Log level:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setLogLevel(Log.DEBUG);
}
}
应用和Libraries都可以注册一些Glide components,包括:
ModelLoader
,用于加载自定义的Model (Urls, Uris, POJOs)和Data (InputStreams, FileDescriptors).ResourceDecoder
,用于解码Resources (Drawables, Bitmaps)或Data (InputStreams, FileDescriptors).Encoder
,用于把Data (InputStreams, FileDescriptors)写入Glide磁盘缓存.ResourceTranscoder
,用于把Resources (BitmapResource)转成其他类型的Resources (DrawableResource).ResourceEncoder
,用于把Resources (BitmapResource, DrawableResource)写入Glide磁盘缓存.
注册components的过程也很简单,只需要在AppGlideModule
或LibraryGlideModule
的registerComponents()
方法中使用Registry
注册即可:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.append(Photo.class, InputStream.class, new CustomModelLoader.Factory());
}
}
资源的加载过程:
一系列已注册的components(包括Glide默认已注册的和Modules中注册的)定义了一系列的加载路径(load paths),每个加载路径都是从load()
方法提供的Model到as()
方法指定的Resource类型的一步步处理,一个加载路径包含以下几个步骤:
1. Model -> Data (ModelLoader
负责)
2. Data -> Resource (ResourceDecoder
负责)
3. Resource -> Transcoded Resource (可选,ResourceTranscoder
负责).
Encoder
可以在第2步之前把Data (InputStreams, FileDescriptors)写入Glide磁盘缓存,ResourceEncoder
可以在第3步之前把Resources (BitmapResource, DrawableResource)写入Glide磁盘缓存。
当一个请求开始后,Glide将尝试所有从Model到请求的Resource类型的可用路径。如果任何一个加载路径成功,这个请求就将成功。只有所有可用加载路径都失败时,这个请求才会失败。
关于Components的顺序:
在加载的过程中,Glide将尝试每个已注册的ModelLoader
、ResourceDecoder
。而尝试的顺序,你可以通过Registry
的prepend()
, append()
, 和replace()
方法设置。
1.prepend()
将确保你的ModelLoader
和ResourceDecoder
先于所有之前注册的components调用,所以当需要处理已存在的Model/Data子集时,你需要使用prepend()
方法。如果你的ModelLoader
或ResourceDecoder
的handles()
方法返回false
或者失败时, 所有其它的ModelLoader
或ResourceDecoder
将按其注册顺序被逐个调用。如当你需要自己处理某一类String类型url的加载时,你应该使用prepend()
方法以便你自定义的ModelLoader
(继承BaseGlideUrlLoader<String>
)先于Glide默认String类型url的ModelLoader
被调用:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.prepend(String.class, InputStream.class, new CustomUrlModelLoader.Factory());
}
}
如果你这个自定义的ModelLoader
load失败了,会交给Glide默认行为继续处理的。
2. append()
将确保你的ModelLoader
和ResourceDecoder
只有在Glide默认选项都尝试后再调用,所以当你需要处理一个新的Model类型时,你需要使用append()
。如当你需要通过你自定义的Model对象(Photo.class)获取InputStream时:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.append(Photo.class, InputStream.class, new CustomModelLoader.Factory());
}
}
3.replace()
将确保Glide所有给定Model和Data的ModelLoader
都被替换成你的ModelLoader
,所以当你不想要Glide默认行为执行,只执行你自己的加载逻辑的时候,你需要使用replace()
。如当你只想使用OkHttp
网络库去请求网络资源时:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
关于配置的冲突:
你的应用可能依赖多个libraries,这些libraries可能包含多个LibraryGlideModule
,有些情况下这些LibraryGlideModule
的options存在冲突或者有些options是你的应用不需要或者避免的,此时你可以给你应用的AppGlideModule
添加@Excludes
注解去解决冲突,如:
@Excludes({com.example.unwanted.GlideModule.class, com.example.conflicing.GlideModule.class})
@GlideModule
public final class MyAppGlideModule extends AppGlideModule { }
@Excludes
注解既可以用于排除LibraryGlideModule
,也可用于排除你之前使用v3版本时定义的GlideModule
。
关于之前使用v3版本时在AndroidManifest.xml中定义的 GlideModule
:
Glide v4为了向后兼容,更平滑的从v3过渡到v4,依然会解析应用和其libraries的AndroidManifest.xml文件并注册其中的GlideModule
。
如果你的应用和其libraries都已经是Glide v4的AppGlideModule
和LibraryGlideModule
,你完全可以禁用manifest解析以提高Glide初始化速度并避免潜在的解析问题:
@GlideModule
public final class MyAppGlideModule extends AppGlideModule {
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
使用技巧
Glide的图片变换(Transformations)
当你使用Glide为ImageView加载图片时,Glide会根据ImageView的ScaleType自动应用相应的变换,如果scale type是CENTER_CROP
,Glide将自动应用CenterCrop
transformation,如果scale type是FIT_CENTER
或CENTER_INSIDE
,Glide将自动应用FitCenter
transformation。
很多情况下,我们需要对要显示的Resources (如Drawables, Bitmaps)进行裁剪、滤镜、模糊等处理,所以Glide内置了几种常用的transformations:
- CenterCrop
- FitCenter
- CenterInside
- CircleCrop
- RoundedCorners
应用这些transformations也很简单:
RequestOptions requestOptions = new RequestOptions()
.fitCenter();
Glide.with(fragment)
.load(url)
.apply(requestOptions)
.into(imageView);
import static com.bumptech.glide.request.RequestOptions.fitCenterTransform;
Glide.with(fragment)
.load(url)
.apply(fitCenterTransform())
.into(imageView);
GlideApp.with(fragment)
.load(url)
.fitCenter()
.into(imageView);
GlideApp.with(fragment)
.load(url)
.transform(new MultiTransformation(new FitCenter(), new YourCustomTransformation())
.into(imageView);
GlideApp.with(fragment)
.load(url)
.transforms(new FitCenter(), new YourCustomTransformation())
.into(imageView);
其实本质都是调用RequestOptions
的transform()
方法,对于多个变换来说,MultiTransformation
构造器的参数顺序决定了变换的应用顺序。
如果需要更多的图片变换可以参考 shangmingchao/GlideTransformation 或 wasabeef/glide-transformations 的自定义Transformations。
Glide的过渡动画(Transitions)
前面已经提到了,Glide的Transitions可以让图片更平滑地展示,但是在Android中使用动画是非常昂贵的,尤其是一次开启很多动画。而Cross fade及其他过渡动画对于透明度的动态改变也是特别昂贵的,更糟糕的情况下动画的执行时间甚至比解码所花费的时间还要长,所以无理由地在列表中使用动画可能会造成卡顿。为了最大化性能,最好避免在ListView
、GridView
或RecyclerView
中使用Glide的加载动画,尤其是你希望图片更快的显示和缓存时。
Glide默认的cross fades动画是基于Android的TransitionDrawable的,TransitionDrawable
提供了两种动画模式,由setCrossFadeEnabled()
方法控制,当cross fades被禁用时,过渡图片会淡入到已显示的图片上面,当cross fades启用时,过渡图片会从透明逐渐变成不透明,之前的图片会从不透明变成透明。
Glide默认是禁用cross fades的,因为启用时两个图片透明度的同时变化可能会产生白色闪烁,还是禁用更好一点。但禁用cross fades也会产生一些问题:当placeholder比要加载的图片大或者要加载的图片是透明的时,禁用cross fades将会导致placeholder显示在要显示的图片下面。 你可以使用new DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true))
启用cross fades。
Android TransitionDrawable
的cross fades动画还有个Bug,如果两张图片的宽高比不一样,会导致图片变形,所以慎用cross fades动画。
Glide的缓存管理(Caching)
Glide在开始一个新的图片请求之前,会检查一下各级缓存:
- Active resources - 要加载的图片是否正显示在其他View上?
- Memory cache - 要加载的图片是否最近加载过且还在内存中?
- Resource - 要加载的图片是否已经被解码、变换且写入过磁盘缓存?
- Data - 要加载的图片的原始data是否可以从磁盘缓存中获取到?
前两步是检查图片资源是否在内存中,如果在则马上返回图片资源。后两步是检查图片资源是否在磁盘缓存中,如果是则尽快异步返回图片资源。
如果这4步都没能获取到图片,Glide才会根据model(如File, Uri, Url)去获取原始资源。
Glide中缓存的cache keys包含很多元素,至少包括model(如File, Uri, Url)和可选的Signature
。事实上,第1-3步(Active resources, memory cache, resource disk cache)的cache keys还是包括图片的宽高、可选的Transformation
、已添加的Options
以及请求的数据类型 (如Bitmap, GIF)等元素,为了生成磁盘缓存上的cache keys名称,cache keys的每个元素都会被哈希化以创建一个String key,并在随后作为磁盘缓存上的文件名使用。
可以使用RequestOptions
的diskCacheStrategy()
方法为某个请求指定磁盘缓存策略,默认的缓存策略是AUTOMATIC
:
AUTOMATIC
- 请求远程数据时只缓存未经更改的原始data,请求本地数据时将缓存变换后的resourceDATA
- 缓存未经decoded的原始dataRESOURCE
缓存decoded后的resourceALL
- 请求远程数据时缓存DATA
和RESOURCE
,请求本地数据时只缓存RESOURCE
NONE
不缓存
GlideApp.with(fragment)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);
有些情况下(如无图模式/省流模式),我们希望Glide只从缓存中获取图片资源,可以借助RequestOptions
的onlyRetrieveFromCache()
方法:
GlideApp.with(fragment)
.load(url)
.onlyRetrieveFromCache(true)
.into(imageView);
跳过内存缓存:
GlideApp.with(fragment)
.load(url)
.skipMemoryCache(true)
.into(view);
跳过磁盘缓存:
GlideApp.with(fragment)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(view);
清除内存缓存:
// 必须在主线程中调用
Glide.get(context).clearMemory();
清除磁盘缓存:
// 必须在工作线程中调用
Glide.get(applicationContext).clearDiskCache();
刷新缓存: 你可以通过RequestOptions
的signature()
方法向内存缓存和磁盘缓存的cache keys中添加额外的数据元素,以更自由地控制缓存失效与刷新。如你想要版本号也作为cache keys的一部分,当版本号更改时刷新这些缓存:
GlideApp.with(yourFragment)
.load(yourFileDataModel)
.signature(new ObjectKey(yourVersionMetadata))
.into(yourImageView);
更多推荐
所有评论(0)