Glide v4详解

来源:互联网 发布:ajaxfileupload php 编辑:程序博客网 时间:2024/06/06 15:00

简介

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)。
Glide logo

下载

在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 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;@GlideModulepublic 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: AppGlideModuleLibraryGlideModule,这两个类都可以注册ModelLoader, ResourceDecoder等额外组件,但只有AppGlideModule允许更改包括缓存位置、大小在内的应用特定设置。
一个应用只能有一个AppGlideModule实现(如果有多个编译时就会报错),所以libraries是不能提供AppGlideModule的实现的。
为了让Glide更好地寻找AppGlideModuleLibraryGlideModule实现,它们的实现类必须用@GlideModule注解,有了这个注解,Glide的annotation processor就可以在编译时找到所有的实现类了。
如果libraries想配置Glide,必须:

  • 添加一到多个LibraryGlideModule实现
  • 每个LibraryGlideModule都添加@GlideModule注解
  • 添加Glide的annotation processor依赖
@GlideModulepublic 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大小和屏幕分辨率决定内存缓存的大小。 你可以自定义缓存实现或缓存大小,如

@GlideModulepublic 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,这个路径是应用内部缓存路径,如果你想要缓存到外部公共目录下,可以这样设置:

@GlideModulepublic class YourAppGlideModule extends AppGlideModule {  @Override  public void applyOptions(Context context, GlideBuilder builder) {    builder.setDiskCache(new ExternalDiskCacheFactory(context));  }}

也可以指定内部/外部磁盘缓存大小、缓存路径:

@GlideModulepublic 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));  }}

甚至可以自定义磁盘缓存的实现:

@GlideModulepublic 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:

@GlideModulepublic 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指定出错时的策略:

@GlideModulepublic 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:

@GlideModulepublic 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注册即可:

@GlideModulepublic 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<String>)先于Glide默认String类型url的ModelLoader被调用:

@GlideModulepublic 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时:

@GlideModulepublic 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网络库去请求网络资源时:

@GlideModulepublic 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})@GlideModulepublic 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初始化速度并避免潜在的解析问题:

@GlideModulepublic 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);
原创粉丝点击