Glide 4.0学习笔记

Glide 4.0使用

Glide v4 使用 注解处理器 (Annotation Processor) 来生成出一个 API,在 Application 模块中可使用该流式 API 一次性调用到 RequestBuilderRequestOptions 和集成库中所有的选项。

有效使用范围

Generated API 目前仅可以在 Application 模块内使用。这一限制可以让我们仅持有一份 Generated API,而不是各个 Library 和 Application 中均有自己定义出来的 Generated API。这一做法会让 Generated API 的调用更简单,并确保 Application 模块中 Generated API 调用的选项在各处行为一致。

导入

annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'  //注解依赖包 
implementation 'com.github.bumptech.glide:glide:4.7.1'  //api包
//Generated API  必须要导入上述注解依赖包才可以使用
@GlideModule   
public class MyAppGlideModule extends AppGlideModule {
}

Generated API

Generated API 默认名为 GlideApp ,与 Application 模块中 AppGlideModule的子类包名相同。在 Application 模块中将 Glide.with() 替换为 GlideApp.with(),即可使用该 API 去完成加载工作

GlideApp.with(fragment)
   .load(myUrl)
   .placeholder(R.drawable.placeholder)
   .fitCenter()
   .into(imageView);

Glide.with() 不同,诸如 fitCenter()placeholder() 等选项在 Builder 中直接可用,并不需要再传入单独的 RequestOptions 对象。

占位符

  • 加载占位符(placeholder): 加载图片时展示,加载失败时没有设置error,显示的还是placeholder

  • 错误符(error):加载失败时展示,如果加载的url为null,且没有设置fallback时展示error

  • 后备回调符(fallback): 在请求的url为 null 时展示,表示允许用户url为null,比如用户头像还未设置时,展示的默认图片

使用

generated api

GlideApp.with(fragment)
  .load(url)
  .placeholder(R.drawable.placeholder)
  .error(R.drawable.error)
  .fallback(R.drawable.fallback)
  .into(view)

Glide方式

RequestOptions options=new RequestOptions();
options
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .fallback(R.drawable.fallback);
Glide.with(this).load(url).apply(options).into(view);

补充

占位符是异步加载的吗?

No。占位符是在主线程从Android Resources加载的。我们通常希望占位符比较小且容易被系统资源缓存机制缓存起来。

变换是否会被应用到占位符上?

No。Transformation仅被应用于被请求的资源,而不会对任何占位符使用。例如你正在加载圆形图片,你可能希望在你的应用中包含圆形的占位符。但是你也可以考虑自定义一个View来剪裁(clip)你的占位符,达到你的transformation的效果。

在多个不同的View上使用相同的Drawable可行么?

通常可以,但不是绝对的。任何无状态(non-stateful)的 Drawable(例如 BitmapDrawable )通常都是ok的。但是有状态的 Drawable 不一样,在同一时间多个 View 上展示它们通常不是很安全,因为多个View会立刻修改(mutate) Drawable 。对于有状态的 Drawable ,建议传入一个资源ID,或者使用 newDrawable() 来给每个请求传入一个新的拷贝。

View的mutate

如果同一个资源文件被两个drawable所使用,那么这两个drawable的状态会保持一致,其中一个drawable改变透明度,另外一个drawable也会跟着改变。这时如果想要两个drawable独立,就需要用到mutate

drawableA.mutate().setAlpha(255); 
drawableB.mutate().setAlpha(70);

RequestOptions

Glide4.0之后的原始api中很多样式的设置都需要通过RequestOptions,包括

  • 占位符(Placeholders)
  • 转换(Transformations)
  • 缓存策略(Caching Strategies)
  • 组件特有的设置项,例如编码质量,或Bitmap的解码配置等。

比如要实现一个CenterCrop转换.

RequestOptions cropOptions = new RequestOptions().centerCrop(context);
...
Glide.with(fragment)
    .load(url)
    .apply(cropOptions)
    .into(imageView);

其中apply()可以调用多次,也就是说你可以配置多个RequestOptions参数,如果 RequestOptions 对象之间存在相互冲突的设置,那么只有最后一个被应用的 RequestOptions 会生效。

另外Glide内部还提供了静态方法来实现CenterCrop转换

import static com.bumptech.glide.request.RequestOptions.centerCropTransform;

Glide.with(fragment)
    .load(url)
    .apply(centerCropTransform(context))
    .into(imageView);

当然如果你使用的是Generated API的话,就可以直接使用

GlideApp.with(fragment)
    .load(url)
    .centerCrop()
    .into(imageView);

图片加载过渡动画(TransitionOptions)

使用 TransitionOption 可以应用以下变换:

  • View淡入
  • 与占位符交叉淡入
  • 或者什么都不发生
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;

Glide.with(fragment)
    .load(url)
    .transition(withCrossFade())   //交叉淡入
    .into(view);

另外TransitionOptions是特定资源类型独有的,即加在bitmapdrawable的时候需要用到不同的api,如 BitmapTransitionOptions ,而不是 DrawableTransitionOptions

请求主干RequestOptions

Glide中请求的发起者是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);
}

缩略图请求

Glide.with(fragment)
  .load(url)
  .thumbnail(Glide.with(fragment)
    .load(thumbnailUrl))
  .into(imageView);

缩略图应改指向一个低分辨率的图片,这样就快速加在并在主体图片加载之前显示。

如果只有一个远程的url地址,可以强制要求加载一个低分辩率的图像,通过sizeMultiplier

Glide.with(this)
        .load("")
        .thumbnail(0.25f)  // 尺寸是View的百分比
        .into(imageView);

在Glide4.3之后还提供了一个失败加载

Glide.with(fragment)
  .load(primaryUrl)
  .error(Glide.with(fragment)
      .load(fallbackUrl))
  .into(imageView);

这个error请求之后在正常请求失败之后才会启动.

多重变换

默认情况下,每个 transform() 调用,或任何特定转换方法(fitCenter(), centerCrop(), bitmapTransform() )的调用都会替换掉之前的变换。

其中 MultiTransformation

Glide.with(fragment)
  .load(url)
  .transform(new MultiTransformation(new FitCenter(), new YourCustomTransformation())
  .into(imageView);

自定义变化

可以选择继承自BitmapTransformation或者DrawableTransitionOptions,

需要注意的是,对于任何 Transformation 子类,包括 BitmapTransformation,你都有三个方法你 必须 实现它们,以使得磁盘和内存缓存正确地工作:

  1. equals()
  2. hashCode()
  3. updateDiskCacheKey

ImageView的自动变换

在Glide中,当你为一个 ImageView 开始加载时,Glide可能会自动应用 FitCenterCenterCrop这取决于view的 ScaleType 。如果 scaleTypeCENTER_CROP , Glide 将会自动应用 CenterCrop 变换。如果 scaleTypeFIT_CENTERCENTER_INSIDE ,Glide会自动使用 FitCenter 变换。

当然,你总有权利覆写默认的变换,只需要一个带有 Transformation 集合的 RequestOptions 即可。另外,你也可以通过使用 dontTransform() 确保不会自动应用任何变换

Gif的加载动画

Glide可以将 Bitmap Transformation应用到 BitmapDrawable , GifDrawable , 以及 Bitmap 资源上,因此通常你只需要编写和应用 Bitmap``Transformation 。然而,如果你添加了额外的资源类型,你可能需要考虑派生 RequestOptions 类,并且,在内置的这些 Bitmap Transformations 之外,你还需要为你的自定义资源类型提供一个 Transformation

目标Target

Glide.with(fragment)
        .load(url)
        .into(imageView);  //into() 开始启动请求

通常情况下,我们是这样使用Glide的,其中into(imageView)这行会将我们期望展示图片的容器ImageView传递进去。这时大部分人会想Glide是将下载好的图片直接赋值给ImgaeView,实际上这中间并不是一步到位的,

ViewTarget<ImageView, Drawable> target = Glide.with(this)
        .load("")
        .into(imageView);

如上述代码所示into(imageView)会返回一个ViewTarget对象,这个ViewTarget能对加载的图片进行各种操作,另外也能返回最开始的imageView,通过

ImageView image=target.getView();

既然target是每个imageView的包装,所以每个target都是唯一的,如果你使用之前的target来加载新的图片,就会重置之前的操作,将前一步加载的资源释放掉。

ViewTarget<ImageView, Drawable> target = Glide.with(this)
                .load(url)
                .into(imageView);
... 
// Some time in the future:
Glide.with(fragment)
  .load(newUrl)
  .into(target);   //这时老的imageView上的图片资源会被释放,新图片会替代来图片展示
  
  方法二
  Glide.with(this).clear(target);

Glide 的 ViewTarget 子类使用了 Android Framework 的 getTag()setTag() 方法来存储每个请求的相关信息,因此在使用Glide时,你还需要使用到setTag()时需要注意是否会出现冲突。

最简单的解决方式是调用 setTag(int,object)

加载目标尺寸

默认情况下,Glide 使用目标通过 getSize 方法提供的尺寸来作为请求的目标尺寸。这允许 Glide 选取合适的 URL,下采样,裁剪和变换合适的图片以减少内存占用,并确保加载尽可能快地完成。

target.getSize(new SizeReadyCallback(width,height));

默认情况下Glide的尺寸加载逻辑

  1. 如果 View 的布局参数尺寸 > 0 且 > padding,则使用该布局参数;
  2. 如果 View 尺寸 > 0 且 > padding,使用该实际尺寸;
  3. 如果 View 布局参数为 wrap_content 且至少已发生一次 layout ,则打印一行警告日志,建议使用 Target.SIZE_ORIGINAL 或通过 override() 指定其他固定尺寸,并使用屏幕尺寸为该请求尺寸;
  4. 其他情况下(布局参数为 match_parent0, 或 wrap_content 且没有发生过 layout ),则等待布局完成,然后回溯到步骤1。

Transition(过渡动画)

Transition指的是从占位符到新加载的图片,或从缩略图到全尺寸图像过渡,而不是从一个请求到另一个请求的动画

另外不同于 Glide v3,Glide v4 将不会默认应用交叉淡入或任何其他的过渡效果。每个请求必须手动应用过渡。

动画的性能提示

Android中的动画代价是比较大的,尤其是同时开始大量动画的时候。 交叉淡入和其他涉及 alpha 变化的动画显得尤其昂贵。 此外,动画通常比图片解码本身还要耗时。在列表和网格中滥用动画可能会让图像的加载显得缓慢而卡顿。为了提升性能,请在使用 Glide 向 ListView , GridView, 或 RecyclerView 加载图片时考虑避免使用动画,尤其是大多数情况下,你希望图片被尽快缓存和加载的时候。作为替代方案,请考虑预加载,这样当用户滑动到具体的 item 的时候,图片已经在内存中了。

在多个请求间交叉淡入

Transitions 并不能让你在不同请求中加载的两个图像之间做过渡。当新的加载被应用到 View 或 Target (查看 Target的文档 )上时,Glide 默认会取消任何已经存在的请求。因此,如果你想加载连个个不同的图片并在它们之间做动画,你无法直接通过 Glide 来完成。等待第一个加载完成并在 View 外持有这个 Bitmap 或 Drawable ,然后开始新的加载并手动在这两者之间做动画,诸如此类的策略看起来有效,但是实际上不安全,并可能导致程序崩溃或图像错误。

相反,最简单的办法是使用包含两个 ImageViewViewSwitcher 来完成。将第一张图片加载到 getNextView() 的返回值里面,然后将第二张图片加载到 getNextView() 的下一个返回值中,并使用一个 RequestListener 在第二张图片加载完成时调用 showNext() 。为了更好地控制,你也可以使用 Android开发者文档 指出的策略。但要记住与 ViewSwitcher 一样,仅在第二次图像加载完成后才开始交叉淡入淡出。

配置

在非application中不能实现AppGlideModule

内存缓存

默认情况下,Glide使用 LruResourceCache ,这是 MemoryCache 接口的一个缺省实现,使用固定大小的内存和 LRU 算法。LruResourceCache 的大小由 Glide 的 MemorySizeCalculator 类来决定,这个类主要关注设备的内存类型,设备 RAM 大小,以及屏幕分辨率。

应用程序可以自定义 MemoryCache 的大小,具体是在它们的 AppGlideModule 中使用 applyOptions(Context, GlideBuilder) 方法配置 MemorySizeCalculator

@GlideModule
public class AppGlideModule extends AppGlideModule {
  @Override
  public void applyOptions(Context context, GlideBuilder builder) {
    MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
        .setMemoryCacheScreens(2)
        .build();
    builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize()));
  }
}

或者

@GlideModule
public class AppGlideModule extends AppGlideModule {
  @Override
  public void applyOptions(Context context, GlideBuilder builder) {
    int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
    builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
  }
}

Bitmap 池

Glide 使用 LruBitmapPool 作为默认的 BitmapPoolLruBitmapPool 是一个内存中的固定大小的 BitmapPool,使用 LRU 算法清理。默认大小基于设备的分辨率和密度,同时也考虑内存类和 isLowRamDevice 的返回值。具体的计算通过 Glide 的 MemorySizeCalculator 来完成,与 Glide 的 MemoryCache的大小检测方法相似

缓存模式

默认情况下Glide会在开始一个新的图片请求之前按顺序检查下列各级缓存
  1. 活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
  2. 内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
  3. 资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?
  4. 数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?

前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。

如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。

缓存的图片的key

在 Glide v4 里,所有缓存键都包含至少两个元素:

  1. 请求加载的 model(File, Url, Url)
  2. 一个可选的 签名(Signature)

另外,步骤1-3(活动资源,内存缓存,资源磁盘缓存)的缓存键还包含一些其他数据,包括:

  1. 宽度和高度
  2. 可选的变换(Transformation)
  3. 额外添加的任何 选项(Options)
  4. 请求的数据类型 (Bitmap, GIF, 或其他)

活动资源和内存缓存使用的键还和磁盘资源缓存略有不同,以适应内存 选项(Options),比如影响 Bitmap 配置的选项或其他解码时才会用到的参数。

为了生成磁盘缓存上的缓存键名称,以上的每个元素会被哈希化以创建一个单独的字符串键名,并在随后作为磁盘缓存上的文件名使用。

磁盘缓存策略(Disk Cache Strateg)

默认是AUTOMATIC模式,当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪–译者注)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。

修改缓存策略
GlideApp.with(fragment)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.ALL)
  .into(imageView);

其他的一些api

GlideApp.with(this)
        .load("")
        .onlyRetrieveFromCache(true)  //仅从缓存加载 (省流量模式)
        .skipMemoryCache(true)        //跳过缓存,加载原始图片 (图片验证码)
        .diskCacheStrategy(DiskCacheStrategy.NONE) //仅跳过磁盘缓存
        .into(imageView);
刷新图片缓存的方式
GlideApp.with(yourFragment)
    .load(yourFileDataModel)
    .signature(new ObjectKey(yourVersionMetadata))
    .into(yourImageView);

加载图片的时候,传入一个可变的signature数据来控制图片的刷新

清理磁盘

new AsyncTask<Void, Void, Void> {
  @Override
  protected Void doInBackground(Void... params) {
    // This method must be called on a background thread.
    Glide.get(applicationContext).clearDiskCache();
    return null;
  }
}

注意需要开启子线程,而且需要传入application以防止内存泄露

Glide 4.0 参考文档

推荐阅读更多精彩内容

  • 【Android 库 Glide】 引用 Android图片加载框架最全解析(一),Glide的基本用法Andro...
    Rtia阅读 1,397评论 0 22
  • 前言 android中图片加载框架有很多,所有框架最终达到的目都是在Android平台上以极度简单的方式加载和展示...
    luoqiang108阅读 11,180评论 6 95
  • 学习来源:郭霖大师博客地址 1、图片加载框架挺多,如Volley、Glide、Picasso、Fresco、本次是...
    子谦宝宝阅读 692评论 0 6
  • Glide笔记 一、简介 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫Glide的图片加载库,作者是bu...
    AndroidMaster阅读 2,258评论 0 25
  • 7.1 压缩图片 一、基础知识 1、图片的格式 jpg:最常见的图片格式。色彩还原度比较好,可以支持适当压缩后保持...
    AndroidMaster阅读 1,606评论 0 12