欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Glide4 使用教程

程序员文章站 2022-06-05 12:46:16
...

Glide4 使用教程

一、前言

记得我刚开始学习 Android 那会,还不懂三方框架,比如 Picasso、Glide、Fresco 等图片加载框架,为了加载一个网络图片老费劲了(也可能是萌新,懂的少),首先要通过网络请求得到一个输入流,然后再通过 BitmapFactory.decodeStream() 方法获取 Bitmap 对象,最后在设置给 ImageView。

时不时还会出现点 OOM 异常给点惊喜,也是很崩溃了==。

后来慢慢了解了三方框架之后,知道了 Google 出品的 Glide。

看下官网的介绍:

Glide 是一个快速高效的 Android 图片加载库,注重于平滑的滚动。Glide 提供了易用的 API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。
Glide 支持拉取,解码和展示视频快照,图片,和GIF动画。Glide的Api是如此的灵活,开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。

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

Glide 可以很方便的加载图片,方便到只需要一行代码:

Glide.with(mContext).load(url).into(mImageView);

至此之后,我就一直在使用 Glide ,虽然在项目中使用过很多 Glide 提供的特性来方便开发,但是过一段时间容易忘记。一直打算写篇文章介绍下 Glide。

正好最近我把 Glide 的版本升级到了 4.0,就决定来写一篇文章来讲下 Glide 的使用,以及在项目中遇到的问题的解决办法。

二、Glide 基本使用

Glide 的 Github 地址:glide

如果你想使用,只需要下面的代码就可以很方便的引入到项目中:

implementation 'com.github.bumptech.glide:glide:4.7.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.7.0'

对了,Glide 是需要使用网络的,别忘了添加网络权限

<uses-permission android:name="android.permission.CAMERA" />

1、简单用法

比如加载一个地址为 url 的图片到 mImageView 中:

Glide.with(mContext).load(url).into(mImageView);

2、占位图

2.1 加载占位图

如果你要加载的图片比较大,需要的时间比较长,那么你可以设置一个默认的图片 default_picture,在真正的图片没加载的时候显示,你可以这样做:

RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.a);
Glide.with(this).load(url).apply(options).into(mImageView);

2.2 错误占位图

如果你不确定图片能不能成功,想在加载错误的时候显示特定的图片,来表示加载失败,你可以这样做:

RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.a)
        .error(R.drawable.d);
Glide.with(this).load(url).apply(options).into(mImageView);

此外,从 Glide 4.3.0 开始,你现在可以使用 error API 来指定一个 RequestBuilder,以在主请求失败时开始一次新的加载,

String errorUrl  ="失败要加载的地址";
        Glide.with(this)
                .load(url)
                .error(Glide.with(this).load(errorUrl))
                .into(mImageView);

这样的话,如果我们加载 url 失败,则会去重新加载 errorUrl。

2.3 null 占位图(后备回调符)

正常情况下,我们的 load() 方法里面的 url 应该不是 null 的,但是如果有可能为 null 的情况,你可以通过设置 fallback() 方法来显示 url 为 null 的情况,代码如下:

RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.a)
        .error(R.drawable.d)
        .fallback(R.drawable.e)
        .override(com.bumptech.glide.request.target.Target.SIZE_ORIGINAL);
Glide.with(this).load(url).apply(options).into(mImageView);

图片显示流程为
1. 正在加载 url 的时候,页面上显示图片 a
2. 加载成功显示 url 指向的图片
3. 加载失败显示 图片 e
4. 如果 url 为 null(一定得是 null ),则显示 图片 e

3、加载指定大小的图片

如果你要加载的图片尺寸是 1080*1920 的,但是你要显示的 ImageView 的大小却是 100 * 100的,这个时候,我们是不用做什么操作的,因为 Glide 会自动的根据 ImageView 的大小来决定加载图片的大小

但是如果你的 ImageView 是 100 * 100 的,而你却要加载的图片大小为 88 * 88,那么你就可以这样写来指定加载的尺寸:

RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.a)
        .error(R.drawable.d)
        .override(88, 88);
Glide.with(this).load(url).apply(options).into(mImageView);

这里需要注意的是,虽然设置了要加载图片的大小,但是设置的 placeholder 和 error 的尺寸是不会变的,还是通过 Glide 根据我们的 ImageView 自动计算的。

当然,你可能说,我不想让 Glide 帮我计算并压缩要加载的图片,我就要加载原始图片大小,当然也是可以的,你可以这样写:

RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.a)
        .error(R.drawable.d)
        .override(com.bumptech.glide.request.target.Target.SIZE_ORIGINAL);
Glide.with(this).load(url).apply(options).into(mImageView);

不过我不建议这样做,因为比如你的原始图片大小 1024 * 1024 的,而你的 ImageView 是 128 * 128 的,那么两种加载方式占用的内存可能会相差几十倍。

4、加载不同格式 Gif、Bitmap、Drawable、File

在 Glide4.0 中有一个 RequestBuilders 的泛型类,用于指定加载资源的格式,可以通过下面四种方法指定,得到不同的 RequestBuilders 对象:

  1. asDrawable() 得到 RequestBuilders
  2. asGif() 得到 RequestBuilders
  3. asBitmap() 得到 RequestBuilders
  4. asFile() 得到 RequestBuilders

默认情况下,如果我们不指定,则是得到一个 RequestBuilders 对象。

比如我们加载 Gif 时可以使用:

RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.a)
        .error(R.drawable.d)
        .override(com.bumptech.glide.request.target.Target.SIZE_ORIGINAL);
// 加载 gif
Glide.with(this).asGif().load(url).apply(options).into(mImageView);
// 加载 bitmap
Glide.with(this).asBitmap().load(url).apply(options).into(mImageView);
// 加载 drawable
Glide.with(this).asDrawable().load(url).apply(options).into(mImageView);

asFile() 和后面要讲的有关系,后面再讲。

5、缩略图的使用

如果在开发中,你要加载的图片很大,并且你要加载的图片有高分辨率版本和低分辨率版本,我们知道,高分辨路意味着图片较大,加载耗时,所以 Glide 给我们提供了 thumbnail 方法来加载缩略图。

thumbnail 方法的参数主要有以下两种:

  1. RequestBuilder thumbnailRequest
  2. float sizeMultiplier

来看下第一种 参数为 RequestBuilder 的:

String highQualityImageUrl = "...";
String lowQualityImageUrl = "...";
Glide.with(this)
        .load(highQualityImageUrl)
        .thumbnail(Glide.with(this)
                .load(lowQualityImageUrl))
        .into(mImageView);

这种情况下会先加载并显示 lowQualityImageUrl 指向的图片,等到 highQualityImageUrl 指向的图片加载完成之后,则显示 highQualityImageUrl 指向的图片。

第二种,参数为 float 的:

看下源码:

public RequestBuilder<TranscodeType> thumbnail(float sizeMultiplier) {
  if (sizeMultiplier < 0f || sizeMultiplier > 1f) {
    throw new IllegalArgumentException("sizeMultiplier must be between 0 and 1");
  }
  this.thumbSizeMultiplier = sizeMultiplier;
  return this;
}

看到我们只能传入 0 到 1 之间的 float 值。用法如下

Glide.with(this)
        .load(highQualityImageUrl)
        .thumbnail(0.5f)
        .into(mImageView);

这种情况下会直接 highQualityImageUrl 指向的图片的一般分辨率的图片,等到 highQualityImageUrl 指向的图片完全加载之后,再显示 highQualityImageUrl 指向的完整图片。

6、Glide 预加载、缓存到硬盘、以及加载监听

6.1 换一种方式加载图片

正常情况下我们都是用下面这行代码加载图片的:

Glide.with(mContext).load(url).into(mImageView);

我们最后的 into() 方法传入了一个 ImageView,实际上这只是 Glide 为我们提供的一个封装,封装成一个 Target 或者 Target 的子类,看下 Glide 的定义:

看下 into(mImageView) 的实现:

@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
  // 。。。省略部分代码
  return into(
      glideContext.buildImageViewTarget(view, transcodeClass),
      /*targetListener=*/ null,
      requestOptions);
}

前面省去的代码是对一些对象的拼装,最终调用的 into 方法:

private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @NonNull RequestOptions options) {

而这里的 TranscodeType 指的就是 设置的 asBitmap()、asDrawable()等之后设置的类型。

实际上一个常规的加载图片的代码可以这样写:

//Target<R> 是一个接口,所以使用其实现类 SimpleTarget 来实现
Glide.with(this).load(url).into(new SimpleTarget<Drawable>() {
    @Override
    public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
        mImageView.setImageDrawable(resource);
    }
});

当然 Target 还有其他的实现类,有兴趣的可以去研究下。

6.2 预加载图片到缓存

如果说我们有两个页面A(当前页面) 和 B(跳转页面),在 B 页面中要使用 Glide 显示一个很大的图片,我们可以在 A 页面的时候就可以先把 B 页面中要加载的图片缓存下来,等到 B 页面的时候,就可以直接从换从中读取了。

示例代码:

下面的 url 均指向相同的地址

在页面 A 中:

Glide.with(this)
        .load(url)
        .listener(new RequestListener<Drawable>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, com.bumptech.glide.request.target.Target<Drawable> target, boolean isFirstResource) {
                Toast.makeText(GlideActivity.this, "加载失败,下个页面从网络取", Toast.LENGTH_SHORT).show();
                return false;
            }

            @Override
            public boolean onResourceReady(Drawable resource, Object model, com.bumptech.glide.request.target.Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                Toast.makeText(GlideActivity.this, "加载成功,下个页面从缓存中取", Toast.LENGTH_SHORT).show();
                return false;
            }
        }).preload();

在页面 B 中:

Glide.with(this).load(url).into(mImageView);

这样当在 A 中提示 “加载成功,下个页面从缓存中取” 的时候,跳转到页面 B ,就可以很快的显示图片了。

6.3 下载图片到指定地址

除了上面的预加载之外,我们也可以先把图片下载到硬盘上,得到一个 File 文件,这个时候要用到 submit() 方法。

源码如下:

@NonNull
/**
 * Returns a future that can be used to do a blocking get on a background thread.
 *
 * <p>This method defaults to {@link Target#SIZE_ORIGINAL} for the width and the height. However,
 * since the width and height will be overridden by values passed to {@link
 * RequestOptions#override(int, int)}, this method can be used whenever {@link RequestOptions}
 * with override values are applied, or whenever you want to retrieve the image in its original
 * size.
 *
 * @see #submit(int, int)
 * @see #into(Target)
 */
@NonNull
public FutureTarget<TranscodeType> submit() {
  return submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}

从源码上的注释来看,这个方法是要会阻塞线程的,所以要在子线程中执行

当调用了submit()方法后会立即返回一个FutureTarget对象,然后Glide会在后台开始下载图片文件。接下来我们调用FutureTarget的get()方法就可以去获取下载好的图片文件了,如果此时图片还没有下载完,那么get()方法就会阻塞住,一直等到图片下载完成才会有值返回

示例代码:

我们使用 AsyncTask 来实现:

    class MyAsyncTask extends AsyncTask<String, Void, File> {

        @Override
        protected File doInBackground(String... strings) {

            FutureTarget<File> target = Glide.with(GlideSecondActivity.this)
                    .asFile()
                    .load(strings[0])
                    .submit();

            try {
                return target.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Toast.makeText(GlideSecondActivity.this, "Glide开始下载图片", Toast.LENGTH_SHORT).show();
        }

        @Override
        protected void onPostExecute(File file) {
            super.onPostExecute(file);
            Log.d(TAG, "图片下载完成,地址为" + file.getAbsolutePath());
            Toast.makeText(GlideSecondActivity.this, "图片下载完成,地址为" + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
            try {
                FileInputStream inputStream = new FileInputStream(file);
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                mImageView.setImageBitmap(bitmap);
                inputStream.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {

            }
        }
    }


    //然后在主线程中调用:

    new MyAsyncTask().execute(url);

这里就用到了我们前面提到的 asFile() 方法了。指定返回一个 File 类型。
可以看到缓存的地址为:

图片下载完成,地址为/data/user/0/com.sean.demo/cache/image_manager_disk_cache/12fd3b7072800c1ef45fce86fb47393e7bfc37b3f19b2b3694132ba9105359a6.0

6.4 加载监听

其实加载监听,上面已经使用过了,就是设置 .listener()方法,传入 Listener 对象。

比如:

Glide.with(this)
        .load(url)
        .listener(new RequestListener<Drawable>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, com.bumptech.glide.request.target.Target<Drawable> target, boolean isFirstResource) {
                Toast.makeText(GlideActivity.this, "加载失败,下个页面从网络取", Toast.LENGTH_SHORT).show();
                return false;
            }

            @Override
            public boolean onResourceReady(Drawable resource, Object model, com.bumptech.glide.request.target.Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                Toast.makeText(GlideActivity.this, "加载成功,下个页面从缓存中取", Toast.LENGTH_SHORT).show();
                return false;
            }
        }).preload();
  • onLoadFailed 方法就是加载失败
  • onResourceReady 就是加载成功

onResourceReady() 方法和 onLoadFailed() 方法都有一个布尔值的返回值,返回 false 就表示这个事件没有被处理,还会继续向下传递,返回 true 就表示这个事件已经被处理掉了,从而不会再继续向下传递。举个简单点的例子,如果我们在 RequestListener 的 onResourceReady() 方法中返回了 true,那么就不会再回调 Target的onResourceReady() 方法了。

7、图片变换

7.1 单次变换

在Glide中,Transformations 可以获取资源并修改它,然后返回被修改后的资源。通常变换操作是用来完成剪裁或对位图应用过滤器,但它也可以用于转换GIF动画,甚至自定义的资源类型。

Glide 内置了几种变换,比如 :

  • CenterCrop
  • FitCenter
  • CircleCrop

比如我们显示圆形图片,我们可以这样做:

RequestOptions options = new RequestOptions().circleCrop();
Glide.with(this).load(url).apply(options).into(mImageView);

显示效果:

Glide4 使用教程

你也可以这样写:

Glide.with(this).load(url).apply(com.bumptech.glide.request.RequestOptions.circleCropTransform()).into(mImageView);

和上面的效果是一样的。

需要注意的的,如果你按照下面的写法:

Glide.with(this)
        .load(url)
        .apply(RequestOptions.centerCropTransform())
        .apply(RequestOptions.circleCropTransform())
        .into(mImageView);

Glide 只会执行后面的变换,业技术 RequestOptions.circleCropTransform 变换。

如果你想一次加载中变换多次,那么你可以这样使用 MultiTransformation。

7.2 多次变换

以下代码可以实现多次变换:

RequestOptions options = new RequestOptions()
        .transform(new MultiTransformation<Bitmap>(new CenterCrop(),new CircleCrop()));
Glide.with(this)
        .load(url)
        .apply(options)
        .into(mImageView);

除了上面介绍的两种变换,你可以自己定义变换,这里推荐一个开源库,就是做 Glide 变换的,有兴趣的可以去看下:
GitHub地址

8、使用 Generated API

GlideModule

如果你不喜欢在使用 Glide4 的时候创建 RequestBuilder、RequestOptions 等对象,而是喜欢 Glide4 之前的链式调用,那么你可以使用 Generated API。

使用 Generated API 必须要先引入以下依赖:

annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1'

这是 Glide 为我们提供的注解处理器,通过这个注解处理器,在 Application 模块中可使用该流式 API 一次性调用到 RequestBuilder, RequestOptions 和集成库中所有的选项,为我们提供类似 Glide3 那样的流式 API。

Generated API 模式的设计出于以下两个目的:

  1. 集成库可以为 Generated API 扩展自定义选项。
  2. 在 Application 模块中可将常用的选项组打包成一个选项在 Generated API 中使用

下面来简单使用下:

首先创建 MyAppGlideModule 继承于 AppGlideModule,并给类添加 @GlideModule 注解:

@GlideModule
public class MyAppGlideModule extends AppGlideModule {
}

然后执行 Rebuild Project操作。

就这样,很简单的你就能使用类似 Glide4 之前的链式调用了。比如:

GlideApp.with(this)
        .load(url)
        .placeholder(R.drawable.a)
        .error(R.drawable.b)
        .into(mImageView);

对比下 Glide4 的用法:

RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.a)
        .error(R.drawable.b);
Glide.with(this)
        .load(url)
        .apply(options)
        .into(mImageView);

使用 Generated API 可以使我们不用去创建 RequestOptions 就可以直接使用 placeholder、error 等方法。

使用GlideExtension

除此之外,Generated API 还支持定制自己的 API 来使用。

比如我项目中的所有使用 Glide 加载动画是不使用动画的,我们就要使用 dontAnimate() 方法,同时还要设置默认的加载错误图片,可以这样写:

新建一个 MyGlideExtension 类,加上 @GlideExtension 注解:

@GlideExtension
public class MyGlideExtension {
    private MyGlideExtension() {
    }

    /**
     * 设置默认error 图片,并禁止动画
     *
     * @param options
     */
    @GlideOption
    public static void defaultError(RequestOptions options) {
        options.placeholder(R.drawable.c)
                .dontAnimate();
    }
}

然后执行 Rebuild Project操作。

这样用的时候就可以这样使用:

GlideApp.with(this)
        .load(url)
        .defaultError()
        .into(mImageView);

当然, 如果你有其他的需求,完全可以在被 @GlideOption 注解的方法里面加上。赶紧去动手定制你想用的 API 吧。

GlideType

被 @GlideType 注解的方法允许你添加对新的资源类型的支持,包括指定默认选项

这个在日常开发中一般是用不上的,如果有需求可以去了解下相关内容。

三、遇到的问题以及解决方法

1、Glide 和 RecyclerView 结合使用出现卡顿

我们经常会遇到在一个 RecyclerView 中加载图片的情况,正常情况下使用的话,在 RecyclerView 滑动的时候,item 回去加载图片,可能 会造成卡顿,那么怎么让 RecyclerView 的滑动更加顺滑呢?

思路基本就是:

在 RecyclerView 滑动的时候,不加载图片,等到 RecyclerView 滑动停止的时候再去执行加载图片的操作。

怎么实现呢?

其实很简单, 我们只要监听 RecyclerView 的滑动就可以了。

// 声明RecyclerView.OnScrollListener
private RecyclerView.OnScrollListener mOnScrollListener;
// RecyclerView 是否在滑动
private boolean sIsScrolling;

/**
 * SCROLL_STATE_DRAGGING      正在滚动
 * SCROLL_STATE_SETTLING      手指做了抛的动作(手指离开屏幕前,用力滑了一下)
 * SCROLL_STATE_IDLE          停止滚动
 */
mOnScrollListener = new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING) {
            sIsScrolling = true;
            Glide.with(RecyclerDemoActivity.this).pauseRequests();
        } else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            if (sIsScrolling) {
                Glide.with(RecyclerDemoActivity.this).resumeRequests();
            }
            sIsScrolling = false;
        }
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
    }
};

recyclerDemo.addOnScrollListener(mOnScrollListener);

@Override
protected void onPause() {
    super.onPause();
    recyclerDemo.removeOnScrollListener(mOnScrollListener);
}

首先定义对 RecyclerView 滑动的监听 mOnScrollListener,以及 RecyclerView 是否在滑动的变量。

然后初始化 mOnScrollListener,并给 RecyclerView 添加此监听。

然后在 Activity 的 onPause 方法中移除对 RecyclerView 滑动监听。

这样,在 RecyclerView 滑动的时候,Glide 就不会去加载图片,只有当滑动完全停止的时候,Glide 才回去加载 item 上的图片。

2、对于指向同一地址的图片显示

假如你要显示的图片,一直指向一个地址,比如 http://www.smartsean.cn/image1,但是这个地址里面的图片可能会有变化,那么,你可能需要禁用 Glide 为我们提供的缓存功能

RequestOptions options = new RequestOptions()
        //禁用磁盘缓存
        .diskCacheStrategy(DiskCacheStrategy.NONE)
        //禁用内存缓存
        .skipMemoryCache(true);

3、Glide的OOM

如果设置 ImageView 的 ScaleType 是 fitxy ,Glide 会默认按照图片实际大小加载。而其他的模式按照的 ImageView 的大小。

如果非要设置 fitxy,那么使用

Glide.with(context).load().centerCrop().into();

或者使用

Glide.with(context).load().fitCenter().into()

后面如果有一些问题,会持续更新

四、总结

从上面的文章可以看出, Glide4 提供的功能还是很强大的,帮助我们做了很多事情,但是我们使用的时候,也不能掉以轻心。

这篇算是我对使用 Glide4 的总结,后续如果有内容会持续更新。