Android|Glide v4详解


    • 简介
    • 下载
    • 配置
    • 使用
      • 简单使用
      • 高级用法
        • RequestOptions
        • TransitionOptions
        • Generated API
        • RequestBuilder
        • Configuration
      • 使用技巧
        • Glide的图片变换Transformations
        • Glide的过渡动画Transitions
        • Glide的缓存管理Caching

简介 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)。
Android|Glide v4详解
文章图片

下载 在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 *; }

你还需要声明联网、磁盘读写权限:

使用 简单使用
很多情况下,使用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 Drawable没有设置,placeholder将继续被显示。
如果请求的url/model为空且error Drawable和fallback Drawable都没设置,placeholder将继续被显示。
error
当请求永远失败时显示error Drawable。
当请求的url/model为空且fallback Drawable没设置时显示error Drawable
fallback
当请求的url/model为空时显示fallback Drawable。
fallback Drawable主要目的是允许用户表明"空"是否被允许,因为Glide默认把空的url/model当做error处理。一个典型的场景就是未设置头像用户的avatar字段可能是 null,而显示的时候我们需要显示一个默认的用户头像,此时设置fallback是个很好地选择。
thumbnail请求
缩略图请求像普通的全尺寸请求一样是一个完整的资源请求,如果缩略图请求在完整请求完成之前完成,就加载并展示缩略图请求请求到的资源。
缩略图由于比全尺寸图更小所以加载的更快,不过无法保证两个请求完成的顺序。
但是,如果缩略图请求在全尺寸请求之后完成,缩略图资源并不会替换全尺寸资源。
error请求
从Glide v4.3.0开始,当你的主请求失败时,你可以通过 error() API指定一个 RequestBuilder去开始一个新的请求。也就是说你可以指定一个备用请求,如果主请求成功完成那么你的error RequestBuilder将不会开始执行。如果你同时指定了 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 可能你已经注意到了,像RequestOptionsTransitionOptions这些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,你可以通过as...方法改变: RequestBuilder requestBuilder = Glide.with(fragment).asBitmap(); RequestBuilder可以被重用去开始多个加载:
RequestBuilder 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: AppGlideModuleLibraryGlideModule,这两个类都可以注册ModelLoader, ResourceDecoder等额外组件,但只有AppGlideModule允许更改包括缓存位置、大小在内的应用特定设置。
一个应用只能有一个AppGlideModule实现(如果有多个编译时就会报错),所以libraries是不能提供AppGlideModule的实现的。
为了让Glide更好地寻找AppGlideModuleLibraryGlideModule实现,它们的实现类必须用@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)); } }

【Android|Glide v4详解】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()); } }

你也可以使用RequestManagerapplyDefaultRequestOptions方法为某个Activity/Fragment应用默认的RequestOptions:
Glide.with(fragment) .applyDefaultRequestOptions( new RequestOptions() .format(DecodeFormat.RGB_565) .disallowHardwareBitmaps());

虽然RequestManager也有setDefaultRequestOptions方法来完全替换之前默认RequestOptions(通过AppGlideModuleRequestManager设置的),但是为了不影响一些默认选项,使用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的过程也很简单,只需要在AppGlideModuleLibraryGlideModuleregisterComponents()方法中使用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将尝试每个已注册的ModelLoaderResourceDecoder。而尝试的顺序,你可以通过Registryprepend(), append(), 和replace()方法设置。
1.prepend()将确保你的ModelLoaderResourceDecoder先于所有之前注册的components调用,所以当需要处理已存在的Model/Data子集时,你需要使用prepend()方法。如果你的ModelLoaderResourceDecoderhandles()方法返回false或者失败时, 所有其它的ModelLoaderResourceDecoder将按其注册顺序被逐个调用。如当你需要自己处理某一类String类型url的加载时,你应该使用prepend()方法以便你自定义的ModelLoader(继承BaseGlideUrlLoader)先于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()将确保你的ModelLoaderResourceDecoder只有在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的AppGlideModuleLibraryGlideModule,你完全可以禁用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_CENTERCENTER_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);

其实本质都是调用RequestOptionstransform()方法,对于多个变换来说,MultiTransformation构造器的参数顺序决定了变换的应用顺序。
如果需要更多的图片变换可以参考 shangmingchao/GlideTransformation 或 wasabeef/glide-transformations 的自定义Transformations。
Glide的过渡动画(Transitions) 前面已经提到了,Glide的Transitions可以让图片更平滑地展示,但是在Android中使用动画是非常昂贵的,尤其是一次开启很多动画。而Cross fade及其他过渡动画对于透明度的动态改变也是特别昂贵的,更糟糕的情况下动画的执行时间甚至比解码所花费的时间还要长,所以无理由地在列表中使用动画可能会造成卡顿。为了最大化性能,最好避免在ListViewGridViewRecyclerView中使用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在开始一个新的图片请求之前,会检查一下各级缓存:
  1. Active resources - 要加载的图片是否正显示在其他View上?
  2. Memory cache - 要加载的图片是否最近加载过且还在内存中?
  3. Resource - 要加载的图片是否已经被解码、变换且写入过磁盘缓存?
  4. 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,并在随后作为磁盘缓存上的文件名使用。
可以使用RequestOptionsdiskCacheStrategy()方法为某个请求指定磁盘缓存策略,默认的缓存策略是AUTOMATIC:
  • AUTOMATIC - 请求远程数据时只缓存未经更改的原始data,请求本地数据时将缓存变换后的resource
  • DATA - 缓存未经decoded的原始data
  • RESOURCE 缓存decoded后的resource
  • ALL - 请求远程数据时缓存DATARESOURCE,请求本地数据时只缓存RESOURCE
  • NONE 不缓存
GlideApp.with(fragment) .load(url) .diskCacheStrategy(DiskCacheStrategy.ALL) .into(imageView);

有些情况下(如无图模式/省流模式),我们希望Glide只从缓存中获取图片资源,可以借助RequestOptionsonlyRetrieveFromCache()方法:
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();

刷新缓存: 你可以通过RequestOptionssignature()方法向内存缓存和磁盘缓存的cache keys中添加额外的数据元素,以更自由地控制缓存失效与刷新。如你想要版本号也作为cache keys的一部分,当版本号更改时刷新这些缓存:
GlideApp.with(yourFragment) .load(yourFileDataModel) .signature(new ObjectKey(yourVersionMetadata)) .into(yourImageView);

    推荐阅读