Glide4 使用教程

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

显示效果:

你也可以这样写:

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 的总结,后续如果有内容会持续更新。

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Sean_css/article/details/79963903
个人分类: 【Android】
上一篇从一个异常认识Android中的 commit() 和 commitAllowingStateLoss()
下一篇使用编译时注解实现简易的 ButterKnife 效果
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭