Android中图片缓存、显示框架Glide的介绍与使用

1 介绍

Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。

Glide 支持拉取,解码和展示视频快照,图片,和GIF动画。Glide的Api是如此的灵活,开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。

虽然Glide 的主要目标是让任何形式的图片列表的滚动尽可能地变得更快、更平滑,但实际上,Glide几乎能满足你对远程图片的拉取/缩放/显示的一切需求。

2 性能

Glide 充分考虑了Android图片加载性能的两个关键方面:

  • 图片解码速度
  • 解码图片带来的资源压力

为了让用户拥有良好的App使用体验,图片不仅要快速加载,而且还不能因为过多的主线程I/O或频繁的垃圾回收导致页面的闪烁和抖动现象。Glide使用了多个步骤来确保在Android上加载图片尽可能的快速和平滑:

自动、智能地下采样(downsampling)和缓存(caching),以最小化存储开销和解码次数;
积极的资源重用,例如字节数组和Bitmap,以最小化昂贵的垃圾回收和堆碎片影响;
深度的生命周期集成,以确保仅优先处理活跃的Fragment和Activity的请求,并有利于应用在必要时释放资源以避免在后台时被杀掉。

3 配置

Gradle

如果使用 Gradle,可从 Maven Central 或 JCenter 中添加对 Glide 的依赖。同样,你还需要添加 Android 支持库的依赖。

repositories {
  mavenCentral()
  maven { url 'https://maven.google.com' }
}

dependencies {
    compile 'com.github.bumptech.glide:glide:4.1.1'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'
}

对于Glide v 4 注意 Android SDK 要求

Min Sdk Version - 使用 Glide 需要 min SDK 版本 API 14 (Ice Cream Sandwich) 或更高。

Compile Sdk Version - Glide 必须使用 API 26 (Oreo) 或更高版本的 SDK 来编译。

4 Proguard

如果你有使用到 proguard,那么请把以下代码添加到你的 proguard.cfg 文件中:

-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 *;
}

5 基本用法

多数情况下,使用Glide加载图片非常简单,一行代码足矣:

Glide.with(fragment)
    .load(myUrl)
    .into(imageView);

取消加载同样很简单:

Glide.with(fragment).clear(imageView);

尽管及时取消不必要的加载是很好的实践,但这并不是必须的操作。实际上,当 Glide.with() 中传入的 Activity 或 Fragment 实例销毁时,Glide 会自动取消加载并回收资源。

6 在ListView 和 RecyclerView 中的使用

在 ListView 或 RecyclerView 中加载图片的代码和在单独的 View 中加载完全一样。Glide 已经自动处理了 View 的复用和请求的取消:

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    String url = urls.get(position);
    Glide.with(fragment)
        .load(url)
        .into(holder.imageView);
}

对 url 进行 null 检验并不是必须的,如果 url 为 null,Glide 会清空 View 的内容,或者显示 placeholder Drawable 或 fallback Drawable 的内容。

Glide 唯一的要求是,对于任何可复用的 View 或 Target ,如果它们在之前的位置上,用 Glide 进行过加载操作,那么在新的位置上要去执行一个新的加载操作,或调用 clear() API 停止 Glide 的工作。

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    if (isImagePosition(position)) {
        String url = urls.get(position);
        Glide.with(fragment)
            .load(url)
            .into(holder.imageView);
    } else {
        Glide.with(fragment).clear(holder.imageView);
        holder.imageView.setImageDrawable(specialDrawable);
    }
}

对 View 调用 clear() 或 into(View),表明在此之前的加载操作会被取消,并且在方法调用完成后,Glide 不会改变 view 的内容。如果你忘记调用 clear(),而又没有开启新的加载操作,那么就会出现这种情况,你已经为一个 view 设置好了一个 Drawable,但该 view 在之前的位置上使用 Glide 进行过加载图片的操作,Glide 加载完毕后可能会将这个 view 改回成原来的内容。

这里的代码以 RecyclerView 的使用为例,但规则同样适用于 ListView。

7 占位图设置

Glide允许用户指定三种不同类型的占位符,分别在三种不同场景使用:

placeholder(占位符)
error(错误符)
fallback(后备回调符)

Placeholder

占位符是当请求正在执行时被展示的 Drawable 。当请求成功完成时,占位符会被请求到的资源替换。如果被请求的资源是从内存中加载出来的,那么占位符可能根本不会被显示。如果请求失败并且没有设置 error Drawable ,则占位符将被持续展示。类似地,如果请求的url/model为 null ,并且 error Drawable 和 fallback 都没有设置,那么占位符也会继续显示。

使用:

Glide.with(fragment)
  .load(url)
  .placeholder(R.drawable.placeholder)
  .into(view);

Or:

Glide.with(fragment)
  .load(url)
  .placeholder(new ColorDrawable(Color.BLACK))
  .into(view);

Error

error Drawable 在请求永久性失败时展示。error Drawable 同样也在请求的url/model为 null ,且并没有设置 fallback Drawable 时展示。

使用:

Glide.with(fragment)
  .load(url)
  .error(R.drawable.error)
  .into(view);

Or:

Glide.with(fragment)
  .load(url)
  .error(new ColorDrawable(Color.RED))
  .into(view);

Fallback

fallback Drawable 在请求的url/model为 null 时展示。设计 fallback Drawable 的主要目的是允许用户指示 null 是否为可接受的正常情况。例如,一个 null 的个人资料 url 可能暗示这个用户没有设置头像,因此应该使用默认头像。然而,null 也可能表明这个元数据根本就是不合法的,或者取不到。 默认情况下Glide将 null 作为错误处理,所以可以接受 null 的应用应当显式地设置一个 fallback Drawable 。

使用:

Glide.with(fragment)
  .load(url)
  .fallback(R.drawable.fallback)
  .into(view);

Or:

Glide.with(fragment)
  .load(url)
  .fallback(new ColorDrawable(Color.GREY))
  .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() 来给每个请求传入一个新的拷贝。

8 选项

请求选项

Glide中的大部分设置项都可以通过 RequestOptions 类和 apply() 方法来应用到程序中。

使用 request options 可以实现(包括但不限于):

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

例如,要应用一个 CenterCrop 转换,你可以使用以下代码:

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

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

如果你想让你的应用的不同部分之间共享相同的加载选项,你也可以初始化一个新的 RequestOptions 对象,并在每次加载时传入这个对象:

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

apply() 方法可以被调用多次,因此 RequestOption 可以被组合使用。如果 RequestOptions 对象之间存在相互冲突的设置,那么只有最后一个被应用的 RequestOptions 会生效。

过渡选项

TransitionOptions 用于决定你的加载完成时会发生什么。

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

  • View淡入
  • 与占位符交叉淡入
  • 或者什么都不发生

如果不使用变换,你的图像将会“跳入”其显示位置,直接替换掉之前的图像。为了避免这种突然的改变,你可以淡入view,或者让多个Drawable交叉淡入,而这些都需要使用TransitionOptions完成。

例如,要应用一个交叉淡入变换:

import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;

Glide.with(fragment)
    .load(url)
    .transition(withCrossFade())
    .into(view);

不同于RequestOptions,TransitionOptions是特定资源类型独有的,你能使用的变换取决于你让Glide加载哪种类型的资源。

这样的结果是,假如你请求加载一个 Bitmap ,你需要使用 BitmapTransitionOptions ,而不是 DrawableTransitionOptions 。同样,当你请求加载 Bitmap 时,你只需要做简单的淡入,而不需要做复杂的交叉淡入。

RequestBuilder

RequestBuilder 是Glide中请求的骨架,负责携带请求的url和你的设置项来开始一个新的加载过程。

使用 RequestBuilder 可以指定:

  • 你想加载的资源类型(Bitmap, Drawable, 或其他)
  • 你要加载的资源地址(url/model)
  • 你想最终加载到的View
  • 任何你想应用的(一个或多个)RequestOption 对象
  • 任何你想应用的(一个或多个)TransitionOption 对象
  • 任何你想加载的缩略图 thumbnail()

你可以通过调用 Glide.with() 来构造一个 RequestBuilder 对象,然后使用某一个 as 方法:

RequestBuilder requestBuilder = Glide.with(fragment).asDrawable();

或使用 Glide.with() 然后 load():

RequestBuilder requestBuilder = Glide.with(fragment).load(url);

选择资源类型

RequestBuilders 是特定于它们将要加载的资源类型的。默认情况下你会得到一个 Drawable RequestBuilder ,但你可以使用 as… 系列方法来改变请求类型。例如,如果你调用了 asBitmap() ,你就将获得一个 BitmapRequestBuilder 对象,而不是默认的 Drawable RequestBuilder。

RequestBuilder<Bitmap> requestBuilder = Glide.with(fragment).asBitmap();

应用 RequestOptions

前面提到,可以使用 apply() 方法应用 RequestOptions ,使用 transition() 方法应用 TransitionOptions 。

RequestBuilder<Drawable> requestBuilder = Glide.with(fragment).asDrawable();
requestBuilder.apply(requestOptions);
requestBuilder.transition(transitionOptions);

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

9 缩略图

Glide 的缩略图功能在这里不得不说,和占位图略有不同,占位图必须使用资源文件才行,而缩略图是动态的占位图可以从网络中加载。缩略图会在世纪请求加载完成或者处理完之后才显示。在原始图片到达之后,缩略图不会取代原始图片,只会被抹除。

Glide 为缩略图提供了2种不同的加载方式,比较简单的方式是调用 thumbnail() 方法,参数是 float 类型,作为其倍数大小。例如,你传入 0.2f 作为参数,Glide 将会显示原始图片的20%的大小,如果原图是 1000x1000 的尺寸,那么缩略图将会是 200x200 的尺寸。为缩略图明显比原图小得多,所以我们需要确保 ImageView 的 ScaleType 设置的正确

Glide.with( context )
    .load( url )
    .thumbnail( 0.2f )
    .into( imageView );

或者

int thumbnailSize = ...;
Glide.with(fragment)
  .load(localUri)
  .thumbnail(Glide.with(fragment)
    .load(localUri)
    .override(thumbnailSize))
  .into(view);

或者

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

在失败时开始新的请求

从 Glide 4.3.0 开始,可以使用 error API 来指定一个 RequestBuilder,以在主请求失败时开始一次新的加载。例如,在请求 primaryUrl 失败后加载 fallbackUrl:

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

如果主请求成功完成,这个error RequestBuilder 将不会被启动。如果你同时指定了一个 thumbnail 和一个 error RequestBuilder,则这个后备的 RequestBuilder 将在主请求失败时启动,即使缩略图请求成功也是如此。

10 图片大小与裁剪

在项目开发过程中,指定图片显示大小长长可能用到,毕竟从服务器获取的图片不一定都是符合设计图的标准的。我们在这里就可以使用 override(width,height) 方法,在图片显示到 ImageView 之前,重新改变图片大小。

Glide.with(context)
    .load(url)
    .override(width,height)//这里的单位是px
    .into(imageView);

在设置图片到 ImageView 的时候,为了避免图片被挤压失真,ImageView 本身提供了 ScaleType 属性,这个属性可以控制图片显示时的方式,具体的属性使用还是去搜索吧!Glide 也提供了两个类似的方法 CenterCrop() 和 FitCenter(),CenterCrop() 方法是将图片按比例缩放到足矣填充 ImageView 的尺寸,但是图片可能会显示不完整;而 FitCenter() 则是图片缩放到小于等于 ImageView 的尺寸,这样图片是显示完整了,但是 ImageView 就可能不会填满了。

注:其实 Glide 的 CenterCrop() 和 FitCenter() 这两个方法分别对应 ImageView 的 ScaleType 属性中的 CENTER_CROP 和 FIT_CENTER 命名基本一致。

11 动画开关

动画效果可以让图片加载变得更加的平滑,crossFade() 方法强制开启 Glide 默认的图片淡出淡入动画。crossFade() 还有一个重载方法 crossFade(int duration)。可以控制动画的持续时间,单位ms。动画默认的持续时间是300ms。既然可以添加动画,那肯定就可以设置没有任何淡出淡入效果,调用 dontAnimate()

Glide.with(context)
    .load(url)
    .crossFade()//或者使用 dontAnimate() 关闭动画
    .placeholder(R.drawable.place_image)
    .error(R.drawable.error_image)
    .into(imageView);

12 缓存

默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:

1.活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
2.内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
3.资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?
4.数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?

前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。
如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。

磁盘缓存策略

目前支持的策略允许你阻止加载过程使用或写入磁盘缓存,选择性地仅缓存无修改的原生数据,或仅缓存变换过的缩略图,或是兼而有之。

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

GlideApp.with(fragment)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.ALL)
  .into(imageView);

DiskCacheStrategy 的枚举意义:
DiskCacheStrategy.NONE 什么都不缓存
DiskCacheStrategy.SOURCE 只缓存全尺寸图
DiskCacheStrategy.RESULT 只缓存最终的加载图
DiskCacheStrategy.ALL 缓存所有版本图(默认行为)

仅从缓存加载图片

某些情形下,你可能希望只要图片不在缓存中则加载直接失败。如果要完成这个目标,你可以在单个请求的基础上使用 onlyRetrieveFromCache 方法:

GlideApp.with(fragment)
  .load(url)
  .onlyRetrieveFromCache(true)
  .into(imageView);

如果图片在内存缓存或在磁盘缓存中,它会被展示出来。否则只要这个选项被设置为 true ,这次加载会视同失败。

跳过缓存

如果你想确保一个特定的请求跳过磁盘和/或内存缓存(比如,图片验证码 –译者注),Glide 也提供了一些替代方案。

仅跳过内存缓存,请使用 skipMemoryCache() :

GlideApp.with(fragment)
  .load(url)
  .skipMemoryCache(true)
  .into(view);

仅跳过磁盘缓存,请使用 DiskCacheStrategy.NONE :

GlideApp.with(fragment)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.NONE)
  .into(view);

这两个选项可以同时使用:

GlideApp.with(fragment)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.NONE)
  .skipMemoryCache(true)
  .into(view);

虽然提供了这些办法让你跳过缓存,但你通常应该不会想这么做。从缓存中加载一个图片,要比拉取-解码-转换成一张新图片的完整流程快得多。

13 图片请求的优先级

同一时间加载多个图片,App 将难以避免这种情况。如果这个时候我们希望用户的体验更好,往往会选择先加载对于用户更加重要的图片。Glide 可以调用 .priority() 方法配合 Priority 枚举来设置图片加载的优先级。

//设置 HIGH 优先级
Glide.with( context )
    .load( highPriorityImageUrl )
    .priority (Priority.HIGH )
    .into( imageView );
//设置 LOW 优先级
Glide.with( context )
    .load( lowPriorityImageUrl )
    .priority( Priority.LOW )
    .into( imageView );
    Priority.LOW
    Priority.NORMAL
    Priority.HIGH
    Priority.IMMEDIAT
这里有一点需要注意,优先级并不是完全严格遵守的。Glide 将会用他们作为一个准则,尽可能的处理这些请求,但是不能保证所有的图片都会按照所有要求的顺序加载。

16 显示 Gif 和 Video

显示 GIf 对于 Glide 来说一个比较特别的功能(至少 Picasso 暂时还不行)而且使用起来非常简单

String gifUrl = "http://i2.mhimg.com/M00/0E/AE/CgAAilTPWJ2Aa_EIACcMxiZi5xE299.gif";
Glide.with( context )
    .load( gifUrl )
    .placeholder( R.drawable.default.error( R.drawable.error )
    .into( imageView );

这段代码还有点问题,如果加载的不是一张 gif 图的话,是没有办法显示的。

Glide.with( context )
    .load( gifUrl )
    .asGif()
    .error( R.drawable.error )
    .into( imageView );

做以上修改,如果图片类型不是 Gif 图的话就会当作 load 失败来处理,因此 error() 会被回调。即使这个url的图片是好的,也是不会显示的。当然,如果你想显示 Gif 但只是向现实静态的图片你就可以这么做

Glide.with( context )
    .load( gifUrl )
    .asBitmap()
    .error( R.drawable.error )
    .into( imageView );

仅仅是显示 Gif 的第一帧图像,这样就可以保证图片的正常显示了。

还有一个神奇的功能,Glide 还能显示视频!But…只能够显示手机本地的视频,要是向现实网络上的视频的话,还是另寻他法吧!

String filePath = "/storrage/emulated/0/Pictures/video.mp4";
Glide.with( context )
    .load( Uri.fromFile( new File( filePath ) ) )
    .into( imageView );

这主要介绍一些Glide主要用法,欢迎批评指正!

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值