图片加载框架Glide


一、Glide基础

1.1 加载图片

图片地址:https://pic.netbian.com/uploads/allimg/240506/232824-1715009304fda0.jpg
布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.henry.imageloadlibrary.ImageloadActivity">

    <ImageView
        android:id="@+id/im1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />


    <ImageView
        android:id="@+id/im2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/im1" />

    <ImageView
        android:id="@+id/im3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/im2" />
</RelativeLayout>

去加载这张图片

public class ImageloadActivity extends AppCompatActivity {

    private ImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_imageload);
        imageView = findViewById(R.id.im1);
        String url ="https://pic.netbian.com/uploads/allimg/240506/232824-1715009304fda0.jpg";
        Glide.with(this).load(url).into(imageView);
    }
}

效果:
在这里插入图片描述

1.2 核心代码

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

首先,Glide.with()方法用于创建一个加载图片的实例。with()方法可以接收Context、Activity或者Fragment类型的参数。选择的范围非常广,不管是在Activity还是Fragment中调用with()方法,都可以直接传this。也可以获取当前应用程序的ApplicationContext,传入到with()方法当中。注意with()方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止。

load()方法,这个方法用于指定待加载的图片资源。Glide支持加载各种各样的图片资源,包括网络图片、本地图片、应用资源、二进制流、Uri对象等等。因此load()方法也有很多个方法重载,除了刚才使用的加载一个字符串网址之外,load()的重载方法:

// 加载本地图片
File file = new File(getExternalCacheDir() + "/image.jpg");
Glide.with(this).load(file).into(imageView);

// 加载应用资源
int resource = R.drawable.image;
Glide.with(this).load(resource).into(imageView);

// 加载二进制流
byte[] image = getImageBytes();
Glide.with(this).load(image).into(imageView);

// 加载Uri对象
Uri imageUri = getImageUri();
Glide.with(this).load(imageUri).into(imageView);

into()方法,希望让图片显示在哪个ImageView上,把这个ImageView的实例传进去就可以了。into()方法不仅仅是只能接收ImageView类型的参数,还支持很多更丰富的用法。

Glide最基本的使用方式,其实就是关键的三步走:先with(),再load(),最后into()。

1.3 占位图

在上述三步的基础上不断进行扩展而已。
占位图就是指在图片的加载过程中,先显示一张临时的图片,等图片加载出来了再替换成要加载的图片。

Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .into(imageView);

Glide当中绝大多数API的用法,其实就是在load()和into()方法之间串接任意想添加的功能就可以了。

因为Glide有非常强大的缓存机制,第一次缓存下来,下次加载的时候将会直接从缓存中读取,不会再去网络下载了,因而加载的速度非常快,所以占位图可能根本来不及显示。修改代码让占位图能有机会显示出来:

Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

diskCacheStrategy()方法,并传入DiskCacheStrategy.NONE参数,这样就可以禁用掉Glide的缓存功能。

除了加载占位图之外,还有一种异常占位图。异常占位图就是因为某些异常情况导致图片加载失败,比如说手机网络信号不好,这个时候就显示这张异常占位图。异常占位图的用法首先准备一张error.jpg图片,然后修改Glide加载部分的代码,如下所示:

Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

1.4 指定图片格式

Glide是支持加载GIF图片的。这一点确实非常牛逼,因为相比之下Picasso是不会支持加载GIF图片的。
并不需要编写什么额外的代码,Glide内部会自动判断图片格式。比如替换uri为一张GIF图片的URL地址即可。

如果希望加载的图必须是一张静态图片,不需要Glide自动判断它到底是静图还是GIF图。只需要再串接一个新的方法就可以了,如下所示:

Glide.with(this)
     .load(url)
     .asBitmap()
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

asBitmap()方法,这个方法只允许加载静态图片,不需要Glide去自动进行图片格式的判断了。现在GIF图无法正常播放了,而是会在界面上显示第一帧的图片。

也可以强制指定加载动态图片。可以这样写:

Glide.with(this)
     .load(url)
     .asGif()
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

指定了只能加载动态图片,而传入的图片却是一张静图的话,那么结果只有加载失败。

1.5 指定图片大小

绝大多数情况下都是不需要指定图片大小的,Glide会自动根据ImageView的大小来决定图片的大小。

内存浪费:指的是在加载图片时,如果图片的像素尺寸远大于在界面上显示的ImageView的尺寸,而没有对图片进行适当的压缩,直接将高像素的图片加载到内存中,导致占用了过多的内存资源,但实际上界面并不需要这么高像素的图片,这种情况就属于内存浪费。因此,为了避免内存浪费,应该根据ImageView的尺寸对图片进行适当的压缩处理,以减少内存占用并提高性能

Glide从来都不会直接将图片的完整尺寸全部加载到内存中,而是用多少加载多少,内部进行了压缩处理,会自动判断ImageView的大小,然后只将这么大的图片像素加载到内存当中,帮助节省内存开支。

给图片指定一个固定的大小,如下所示:

Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .override(100, 100)
     .into(imageView);

二、Glide缓存机制

Glide又将它分成了两个模块,一个是内存缓存,一个是硬盘缓存。
内存缓存的主要作用是防止应用重复将图片数据读取到内存当中。
而硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。

2.1 内存缓存

默认情况Glide自动开启内存缓存。当使用Glide加载了一张图片之后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除之前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。比方在一个RecyclerView当中反复上下滑动,RecyclerView中只要是Glide加载过的图片都可以直接从内存当中迅速读取并展示出来,从而大大提升了用户体验。

禁用内存缓存功能,Glide对此提供了接口:

Glide.with(this)
     .load(url)
     .skipMemoryCache(true)
     .into(imageView);

Glide内存缓存的实现是使用的LruCache算法。除了LruCache算法之外,Glide还结合了一种弱引用的机制,共同完成了内存缓存功能。

2.2 硬盘缓存

禁止Glide对图片进行硬盘缓存使用了如下代码:

Glide.with(this)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);

调用diskCacheStrategy()方法并传入DiskCacheStrategy.NONE,就禁用掉Glide的硬盘缓存功能了。

diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它可以接收四种参数:

DiskCacheStrategy.NONE: 表示不缓存任何内容。
DiskCacheStrategy.SOURCE: 表示只缓存原始图片。
DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)。
DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。

当使用Glide去加载一张图片的时候,Glide默认并不会将原始图片展示出来,而是会对图片进行压缩和转换。总之就是经过种种一系列操作之后得到的图片,即转换过后的图片。而Glide默认情况下在硬盘缓存的就是转换过后的图片,通过调用diskCacheStrategy()方法则可以改变这一默认行为。

硬盘缓存的实现也是使用的LruCache算法。

2.3 重写getCacheKey()

在Glide中,getCacheKey()方法用于生成缓存键,用于唯一标识加载的资源。通常情况下,Glide会根据加载的资源的类型、地址等信息自动生成缓存键,以确保同一资源只会被缓存一次,避免重复加载。
需要重写getCacheKey()方法的情况通常是在加载的资源可能会根据特定条件变化时,需要根据这些条件生成不同的缓存键。例如:

  • 当加载的资源地址可能会变化时:如果加载的资源地址可能会根据不同条件变化,您可以重写getCacheKey()方法,根据不同的条件生成不同的缓存键,以确保不同的资源地址对应不同的缓存。
  • 当加载的资源需要根据特定条件进行缓存时:如果希望根据特定条件对加载的资源进行缓存,可以通过重写getCacheKey()方法,根据这些条件生成缓存键,以实现定制化的缓存策略。

创建一个MyGlideUrl继承自GlideUrl,重写getCacheKey()方法,在里面加入了一段逻辑用于将图片url地址中token(或其他实时影响url变化)参数的这一部分移除掉。这样getCacheKey()方法得到的就是一个没有token参数的url地址,从而不管token怎么变化,最终Glide的缓存Key都是固定不变的了。

使用时,将加载图片的代码改成如下方式即可:

Glide.with(this)
     .load(new MyGlideUrl(url))
     .into(imageView);

三、Glide的回调与监听

3.1 回调源码追溯

  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
  ......
    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
  }
  public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }
public class ImageViewTargetFactory {
  @NonNull
  @SuppressWarnings("unchecked")
  public <Z> ViewTarget<ImageView, Z> buildTarget(
      @NonNull ImageView view, @NonNull Class<Z> clazz) {
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }
}

通过into方法的源码追溯,构建出来的Target对象调用了target.onResourceReady()方法,target继承自ViewTarget对象,看一下它的源码(不同版本target对象可能略有区别,但本质还是调用接口target的onResourceReady):

public class GlideDrawableImageViewTarget extends ImageViewTarget<GlideDrawable> {
    ...
    @Override
    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
        if (!resource.isAnimated()) {
            float viewRatio = view.getWidth() / (float) view.getHeight();
            float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight();
            if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN
                    && Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {
                resource = new SquaringDrawable(resource, view.getWidth());
            }
        }
        super.onResourceReady(resource, animation);
        this.resource = resource;
        resource.setLoopCount(maxLoopCount);
        resource.start();
    }

    @Override
    protected void setResource(GlideDrawable resource) {
        view.setImageDrawable(resource);
    }
    ...
}

在这里插入图片描述
在onResourceReady()方法中处理了图片展示,还有GIF播放的逻辑,那么一张图片也就显示出来了,这也就是Glide回调的基本实现原理。

3.2 into

into()方法中是可以传入ImageView的,into()方法还有一个接收Target参数的重载。即使传入的参数是ImageView,Glide也会在内部自动构建一个Target对象。如果能够掌握自定义Target技术的话,就可以更加随心所欲地控制Glide的回调了。

Target的继承结构还是相当复杂的,实现Target接口的子类非常多。要进行自定义的话,通常只需要在两种Target的基础上去自定义就可以了,一种是SimpleTarget,一种是ViewTarget。(可能已经过时)
在这里插入图片描述SimpleTarget是一种极为简单的Target,使用它可以将Glide加载出来的图片对象获取到,而不是像之前那样只能将图片在ImageView上显示出来。

SimpleTarget的用法示例非常简单:

SimpleTarget<GlideDrawable> simpleTarget = new SimpleTarget<GlideDrawable>() {
    @Override
    public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {
        imageView.setImageDrawable(resource);
    }
};

public void loadImage(View view) {
    String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
    Glide.with(this)
         .load(url)
         .into(simpleTarget);
}

在onResourceReady()方法中,获取到Glide加载出来的图片对象,可以使用它进行任意的逻辑操作,这里只是简单地把它显示到了ImageView上。
SimpleTarget的实现创建好了,只需要在加载图片的时候将它传入到into()方法中就可以了。
SimpleTarget中的泛型并不一定只能是GlideDrawable,如果确定正在加载的是一张静态图而不是GIF图的话,还能直接拿到这张图的Bitmap对象,如下所示:

SimpleTarget<Bitmap> simpleTarget = new SimpleTarget<Bitmap>() {
    @Override
    public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
        imageView.setImageBitmap(resource);
    }
};

public void loadImage(View view) {
    String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
    Glide.with(this)
         .load(url)
         .asBitmap()
         .into(simpleTarget);
}

看一下ViewTarget的用法。

ViewTarget的功能更加广泛,它可以作用在任意的View上。
比如创建了一个自定义布局MyLayout,如下所示:

public class MyLayout extends LinearLayout {

    private ViewTarget<MyLayout, GlideDrawable> viewTarget;

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        viewTarget = new ViewTarget<MyLayout, GlideDrawable>(this) {
            @Override
            public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {
                MyLayout myLayout = getView();
                myLayout.setImageAsBackground(resource);
            }
        };
    }

    public ViewTarget<MyLayout, GlideDrawable> getTarget() {
        return viewTarget;
    }

    public void setImageAsBackground(GlideDrawable resource) {
        setBackground(resource);
    }

}

然后在onResourceReady()方法中,通过getView()方法获取到MyLayout的实例,并调用它的任意接口了。比如说这里调用了setImageAsBackground()方法来将加载出来的图片作为MyLayout布局的背景图。

由于MyLayout中已经提供了getTarget()接口,只需要在加载图片的地方这样写就可以了:

public class MainActivity extends AppCompatActivity {

    MyLayout myLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myLayout = (MyLayout) findViewById(R.id.background);
    }

    public void loadImage(View view) {
        String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
        Glide.with(this)
             .load(url)
             .into(myLayout.getTarget());
    }

}

这些都是自定义Target最基本的用法,掌握了这些用法之后,就能应对各种各样复杂的逻辑了。

3.3 preload()方法

Glide加载图片虽说非常智能,会自动判断该图片是否已经有缓存了,有的话就直接从缓存中读取,没有的话再从网络去下载。但是如果希望提前对图片进行一个预加载,等真正需要加载图片的时候就直接从缓存中读取,不想再等待慢长的网络加载时间了,这该怎么办呢?
into()方法中除了传入ImageView之后还可以传入Target对象,如果在Target对象的onResourceReady()方法中做一个空实现,也就是不做任何逻辑处理,那么图片自然也就显示不出来了,而Glide的缓存机制却仍然还会正常工作,这样不就实现预加载功能了吗。
上述的做法完全可以实现预加载功能,不过这种实现方式有点笨笨的。事实上,Glide专门给我们提供了预加载的接口,也就是preload()方法,只需要直接使用就可以了。

preload()方法有两个方法重载,一个不带参数,表示将会加载图片的原始尺寸,另一个可以通过参数指定加载图片的宽和高。
preload()方法的用法非常简单,直接使用它来替换into()方法即可,如下所示:

Glide.with(this)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy.SOURCE)
     .preload();

使用了preload()方法,最好要将diskCacheStrategy的缓存策略指定成DiskCacheStrategy.SOURCE。因为preload()方法默认是预加载的原始图片大小,而into()方法则默认会根据ImageView控件的大小来动态决定加载图片的大小。因此,如果不将diskCacheStrategy的缓存策略指定成DiskCacheStrategy.SOURCE的话,很容易会造成在预加载完成之后再使用into()方法加载图片,却仍然还是要从网络上去请求图片这种现象。

调用了预加载之后,再去加载这张图片就会非常快了,因为Glide会直接从缓存当中去读取图片并显示出来,代码如下所示:

Glide.with(this)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy.SOURCE)
     .into(imageView);

注意,这里我们仍然需要使用diskCacheStrategy()方法将硬盘缓存策略指定成DiskCacheStrategy.SOURCE,以保证Glide一定会去读取刚才预加载的图片缓存。preload()方法的用法大概就是这么简单。

3.4 downloadOnly()方法

和preload()方法类似,downloadOnly()方法也是可以替换into()方法的,不过downloadOnly()方法的用法明显要比preload()方法复杂不少。顾名思义,downloadOnly()方法表示只会下载图片,而不会对图片进行加载。当图片下载完成之后,可以得到图片的存储路径,以便后续进行操作。

看下基本用法。downloadOnly()方法是定义在DrawableTypeRequest类当中的,它有两个方法重载,一个接收图片的宽度和高度,另一个接收一个泛型对象,如下所示:

downloadOnly(int width, int height)
downloadOnly(Y target)

两个方法各自有各自的应用场景,其中downloadOnly(int width, int height)是用于在子线程中下载图片的,而downloadOnly(Y target)是用于在主线程中下载图片的。

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

public void downloadImage(View view) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
                final Context context = getApplicationContext();
                FutureTarget<File> target = Glide.with(context)
                                                 .load(url)
                                                 .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
                final File imageFile = target.get();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, imageFile.getPath(), Toast.LENGTH_LONG).show();
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
}

downloadOnly(int width, int height)方法必须要用在子线程当中,这里的第一步就是new了一个Thread。在子线程当中,先获取了一个Application Context,这个时候不能再用Activity作为Context了,因为会有Activity销毁了但子线程还没执行完这种可能出现。

接下来就是Glide的基本用法,只不过将into()方法替换成了downloadOnly()方法。downloadOnly()方法会返回一个FutureTarget对象,这个时候其实Glide已经开始在后台下载图片了,随时都可以调用FutureTarget的get()方法来获取下载的图片文件,只不过如果图片还没下载好线程会暂时阻塞住,等下载完成了才会把图片的File对象返回。

最后使用runOnUiThread()切回到主线程,然后使用Toast将下载好的图片文件路径显示出来。

之后可以使用如下代码去加载这张图片,图片就会立即显示出来,而不用再去网络上请求了:

public void loadImage(View view) {
    String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
    Glide.with(this)
            .load(url)
            .diskCacheStrategy(DiskCacheStrategy.SOURCE)
            .into(imageView);
}

这里必须将硬盘缓存策略指定成DiskCacheStrategy.SOURCE或者DiskCacheStrategy.ALL,否则Glide将无法使用刚才下载好的图片缓存文件。

再看一下downloadOnly(Y target)方法。
其实downloadOnly(int width, int height)方法必须使用在子线程当中,主要还是因为它在内部帮我们自动创建了一个RequestFutureTarget,是这个RequestFutureTarget要求必须在子线程当中执行。而downloadOnly(Y target)方法则要求传入一个自己创建的Target,因此就不受RequestFutureTarget的限制了。

但是downloadOnly(Y target)方法的用法也会相对更复杂一些,因为又要自己创建一个Target了,而且这次必须直接去实现最顶层的Target接口,比之前的SimpleTarget和ViewTarget都要复杂不少。

实现一个最简单的DownloadImageTarget吧,注意Target接口的泛型必须指定成File对象,这是downloadOnly(Y target)方法要求的,代码如下所示:

public class DownloadImageTarget implements Target<File> {

    private static final String TAG = "DownloadImageTarget";

    @Override
    public void onStart() {
    }

    @Override
    public void onStop() {
    }

    @Override
    public void onDestroy() {
    }

    @Override
    public void onLoadStarted(Drawable placeholder) {
    }

    @Override
    public void onLoadFailed(Exception e, Drawable errorDrawable) {
    }

    @Override
    public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {
        Log.d(TAG, resource.getPath());
    }

    @Override
    public void onLoadCleared(Drawable placeholder) {
    }

    @Override
    public void getSize(SizeReadyCallback cb) {
        cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
    }

    @Override
    public void setRequest(Request request) {
    }

    @Override
    public Request getRequest() {
        return null;
    }
}

由于是要直接实现Target接口,需要重写的方法非常多。这些方法大多是数Glide加载图片生命周期的一些回调,可以不用管它们,其中只有两个方法是必须实现的,一个是getSize()方法,一个是onResourceReady()方法。

在getSize()方法中就直接回调了Target.SIZE_ORIGINAL,表示图片的原始尺寸。

然后onResourceReady()方法就非常熟悉了,图片下载完成之后就会回调到这里,在这个方法中只是打印了一下下载的图片文件的路径。

这样一个最简单的DownloadImageTarget就定义好了,使用它也非常的简单,不用再考虑什么线程的问题了,而是直接把它的实例传入downloadOnly(Y target)方法中即可,如下所示:

public void downloadImage(View view) {
    String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
    Glide.with(this)
            .load(url)
            .downloadOnly(new DownloadImageTarget());
}

这样就使用了downloadOnly(Y target)方法同样获取到下载的图片文件的缓存路径了。

3.5 listener()方法

listener()方法的作用非常普遍,它可以用来监听Glide加载图片的状态。比如说刚才使用了preload()方法来对图片进行预加载,但是怎样确定预加载有没有完成呢?还有如果Glide加载图片失败了,该怎样调试错误的原因呢?答案都在listener()方法当中。

首先来看下listener()方法的基本用法吧,不同于刚才几个方法都是要替换into()方法的,listener()是结合into()方法一起使用的,当然也可以结合preload()方法一起使用。最基本的用法如下所示:

public void loadImage(View view) {
    String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
    Glide.with(this)
            .load(url)
            .listener(new RequestListener<String, GlideDrawable>() {
                @Override
                public boolean onException(Exception e, String model, Target<GlideDrawable> target,
                    boolean isFirstResource) {
                    return false;
                }

                @Override
                public boolean onResourceReady(GlideDrawable resource, String model,
                    Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                    return false;
                }
            })
            .into(imageView);
}

在into()方法之前串接了一个listener()方法,然后实现了一个RequestListener的实例。其中RequestListener需要实现两个方法,一个onResourceReady()方法,一个onException()方法。从方法名上就可以看出来了,当图片加载完成的时候就会回调onResourceReady()方法,而当图片加载失败的时候就会回调onException()方法,onException()方法中会将失败的Exception参数传进来,这样我们就可以定位具体失败的原因了。

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

四、图片变换

在没有明确指定的情况下,ImageView默认的scaleType是FIT_CENTER

Glide提供了专门的API来添加和取消图片变换,只需要使用如下代码即可:

Glide.with(this)
     .load(url)
     .dontTransform()
     .into(imageView);

但是dontTransform()方法存在着一个问题,就是调用这个方法之后,所有的图片变换操作就全部失效了,如果有一些图片变换操作是必须要执行的,这种情况下需要借助override()方法强制将图片尺寸指定成原始大小就可以了,代码如下所示:

Glide.with(this)
     .load(url)
     .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
     .into(imageView);

4.1 图片变换基础

图片变换:Glide从加载了原始图片到最终展示给用户之前,又进行了一些变换处理,从而能够实现一些更加丰富的图片效果,如图片圆角化、圆形化、模糊化等等。

添加图片变换的用法非常简单,只需要调用transform()方法,并将想要执行的图片变换操作作为参数传入transform()方法即可,如下所示:

Glide.with(this)
     .load(url)
     .transform(...)
     .into(imageView);

Glide已经内置了两种图片变换操作,可以直接拿来使用,一个是CenterCrop,一个是FitCenter。
但这两种内置的图片变换操作其实都不需要使用transform()方法,Glide为了方便使用直接提供了现成的API:

Glide.with(this)
     .load(url)
     .centerCrop()
     .into(imageView);

Glide.with(this)
     .load(url)
     .fitCenter()
     .into(imageView);

centerCrop()和fitCenter()方法其实也只是对transform()方法进行了一层封装而已。

FitCenter的效果就是会将图片按照原始的长宽比充满全屏。
CenterCrop是是对原图的中心区域进行裁剪后得到的图片。
centerCrop()方法还可以配合override()方法来实现更加丰富的效果,比如指定图片裁剪的比例:

String url = "http://cn.bing.com/az/hprichbg/rb/AvalancheCreek_ROW11173354624_1920x1080.jpg";
Glide.with(this)
     .load(url)
     .override(500, 500)
     .centerCrop()
     .into(imageView);

将图片的尺寸设定为500*500像素,那么裁剪的比例也就变成1:1了。

4.2 自定义图片变换

Glide定制好了一个图片变换的框架,可以获取到原始的图片,然后对图片进行变换,再将变换完成后的图片返回给Glide,最终由Glide将图片显示出来。理论上,在对图片进行变换这个步骤中可以进行任何的操作,包括圆角化、圆形化、黑白化、模糊化等等,甚至将原图片完全替换成另外一张图都是可以的。

对图片进行圆形化变换:
图片圆形化的功能现在在手机应用中非常常见,比如手机QQ就会将用户的头像进行圆形化变换,从而使得界面变得更加好看。

自定义图片变换功能的实现逻辑比较固定,自定义的过程其实就是自定义一个类让它继承自BitmapTransformation ,然后重写transform()方法,并在这里去实现具体的图片变换逻辑就可以了。一个空的图片变换实现大概如下所示:

public class CircleCrop extends BitmapTransformation {

    public CircleCrop(Context context) {
        super(context);
    }

    public CircleCrop(BitmapPool bitmapPool) {
        super(bitmapPool);
    }

    @Override
    public String getId() {
        return "com.example.glidetest.CircleCrop";
    }

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        return null;
    }
}

getId()方法中要求返回一个唯一的字符串来作为id,以和其他的图片变换做区分。通常情况下,直接返回当前类的完整类名就可以了。

另外,选择继承BitmapTransformation还有一个限制,就是只能对静态图进行图片变换。如果有特殊的需求要对GIF图进行图片变换,那就得去自己实现Transformation接口才可以了。这个就非常复杂了

实现对图片进行圆形化变换的功能,代码如下所示:

public class CircleCrop extends BitmapTransformation {

    public CircleCrop(Context context) {
        super(context);
    }

    public CircleCrop(BitmapPool bitmapPool) {
        super(bitmapPool);
    }

    @Override
    public String getId() {
        return "com.example.glidetest.CircleCrop";
    }

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        int diameter = Math.min(toTransform.getWidth(), toTransform.getHeight());

        final Bitmap toReuse = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
        final Bitmap result;
        if (toReuse != null) {
            result = toReuse;
        } else {
            result = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888);
        }

        int dx = (toTransform.getWidth() - diameter) / 2;
        int dy = (toTransform.getHeight() - diameter) / 2;
        Canvas canvas = new Canvas(result);
        Paint paint = new Paint();
        BitmapShader shader = new BitmapShader(toTransform, BitmapShader.TileMode.CLAMP, 
                                            BitmapShader.TileMode.CLAMP);
        if (dx != 0 || dy != 0) {
            Matrix matrix = new Matrix();
            matrix.setTranslate(-dx, -dy);
            shader.setLocalMatrix(matrix);
        }
        paint.setShader(shader);
        paint.setAntiAlias(true);
        float radius = diameter / 2f;
        canvas.drawCircle(radius, radius, radius, paint);

        if (toReuse != null && !pool.put(toReuse)) {
            toReuse.recycle();
        }
        return result;
    }
}

对transform()方法中的逻辑做下简单的解释。
第18行先算出原图宽度和高度中较小的值,因为对图片进行圆形化变换肯定要以较小的那个值作为直径来进行裁剪。
第20-26行则和刚才一样,从Bitmap缓存池中尝试获取一个Bitmap对象来进行重用,如果没有可重用的Bitmap对象的话就创建一个。
第28-41行是具体进行圆形化变换的部分,这里算出了画布的偏移值,并且根据刚才得到的直径算出半径来进行画圆。
最后,尝试将复用的Bitmap对象重新放回到缓存池当中,并将圆形化变换后的Bitmap对象进行返回。
这样,一个自定义图片变换的功能就写好了,使用方法非常简单,把这个自定义图片变换的实例传入到transform()方法中即可,如下所示:

Glide.with(this)
     .load(url)
     .transform(new CircleCrop(this))
     .into(imageView);

4.3 其他图片变换功能

网上出现了很多Glide的图片变换开源库,其中做的最出色的应该要数glide-transformations这个库了。它实现了很多通用的图片变换效果,如裁剪变换、颜色变换、模糊变换等等,可以非常轻松地进行各种各样的图片变换。

glide-transformations的项目主页地址是transformations

导入依赖

dependencies {
    compile 'jp.wasabeef:glide-transformations:2.0.2'
}

如果想对图片进行模糊化处理,那么就可以使用glide-transformations库中的BlurTransformation这个类,代码如下所示:

Glide.with(this)
     .load(url)
     .bitmapTransform(new BlurTransformation(this))
     .into(imageView);

注意这里用的是bitmapTransform()方法而不是transform()方法,因为glide-transformations库都是专门针对静态图片变换来进行设计的。

再试一下图片黑白化的效果,使用的是GrayscaleTransformation这个类,代码如下所示:

Glide.with(this)
     .load(url)
     .bitmapTransform(new GrayscaleTransformation(this))
     .into(imageView);

而且还可以将多个图片变换效果组合在一起使用,比如同时执行模糊化和黑白化的变换:

Glide.with(this)
     .load(url)
     .bitmapTransform(new BlurTransformation(this), new GrayscaleTransformation(this))
     .into(imageView);

可以看到,同时执行多种图片变换的时候,只需要将它们都传入到bitmapTransform()方法中即可。

五、带进度的Glide图片加载功能

5.1 替换通讯组件

新建一个OkHttpFetcher类,实现DataFetcher接口,代码如下所示:

public class OkHttpFetcher implements DataFetcher<InputStream> {

    private final OkHttpClient client;
    private final GlideUrl url;
    private InputStream stream;
    private ResponseBody responseBody;
    private volatile boolean isCancelled;

    public OkHttpFetcher(OkHttpClient client, GlideUrl url) {
        this.client = client;
        this.url = url;
    }

    @Override
    public InputStream loadData(Priority priority) throws Exception {
        Request.Builder requestBuilder = new Request.Builder()
                .url(url.toStringUrl());
        for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
            String key = headerEntry.getKey();
            requestBuilder.addHeader(key, headerEntry.getValue());
        }
        Request request = requestBuilder.build();
        if (isCancelled) {
            return null;
        }
        Response response = client.newCall(request).execute();
        responseBody = response.body();
        if (!response.isSuccessful() || responseBody == null) {
            throw new IOException("Request failed with code: " + response.code());
        }
        stream = ContentLengthInputStream.obtain(responseBody.byteStream(),
                responseBody.contentLength());
        return stream;
    }

    @Override
    public void cleanup() {
        try {
            if (stream != null) {
                stream.close();
            }
            if (responseBody != null) {
                responseBody.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getId() {
        return url.getCacheKey();
    }

    @Override
    public void cancel() {
        isCancelled = true;
    }

}

新建一个OkHttpGlideUrlLoader类,并且实现ModelLoader。

public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> { 

    private OkHttpClient okHttpClient; 

    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> { 

        private OkHttpClient client; 

        public Factory() { 
        } 

        public Factory(OkHttpClient client) { 
            this.client = client; 
        } 

        private synchronized OkHttpClient getOkHttpClient() { 
            if (client == null) { 
                client = new OkHttpClient(); 
            } 
            return client; 
        } 

        @Override 
        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new OkHttpGlideUrlLoader(getOkHttpClient()); 
        } 

        @Override 
        public void teardown() { 
        } 
    } 

    public OkHttpGlideUrlLoader(OkHttpClient client) { 
        this.okHttpClient = client; 
    } 

    @Override 
    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) { 
        return new OkHttpFetcher(okHttpClient, model); 
    } 
}

新建一个MyGlideModule类并实现GlideModule接口,然后在registerComponents()方法中将刚刚创建的OkHttpGlideUrlLoader和OkHttpFetcher注册到Glide当中,将原来的HTTP通讯组件给替换掉,如下所示:

public class MyGlideModule implements GlideModule { 
    @Override 
    public void applyOptions(Context context, GlideBuilder builder) { 
    } 

    @Override 
    public void registerComponents(Context context, Glide glide) { 
        glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
    } 
}

识别自定义的MyGlideModule,在AndroidManifest.xml文件当中加入如下配置:

<manifest> 
    ... 
    <application> 
        <meta-data 
            android:name="com.example.glideprogresstest.MyGlideModule" 
            android:value="GlideModule" /> 
        ... 
    </application> 
</manifest>

5.2 实现下载进度监听

将HTTP通讯组件替换成OkHttp之后,依靠OkHttp强大的拦截器机制加入一些自己的逻辑来计算下载进度,这样就可以实现下载进度监听的功能了。
拦截器属于OkHttp的高级功能,它本身并不难。
首先创建一个没有任何逻辑的空拦截器,新建ProgressInterceptor类并实现Interceptor接口,代码如下所示:

public class ProgressInterceptor implements Interceptor { 

    @Override 
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request(); 
        Response response = chain.proceed(request); 
        return response; 
    } 

}

拦截器中什么都没有做。就是拦截到了OkHttp的请求,然后调用proceed()方法去处理这个请求,最终将服务器响应的Response返回。
需要启用这个拦截器,修改MyGlideModule中的代码,如下所示:

public class MyGlideModule implements GlideModule { 
    @Override 
    public void applyOptions(Context context, GlideBuilder builder) { 
    } 

    @Override 
    public void registerComponents(Context context, Glide glide) { 
        OkHttpClient.Builder builder = new OkHttpClient.Builder(); 
        builder.addInterceptor(new ProgressInterceptor()); 
        OkHttpClient okHttpClient = builder.build(); 
        glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory(okHttpClient));
    } 
}

创建一个OkHttpClient.Builder,调用addInterceptor()方法将刚才创建的ProgressInterceptor添加进去,最后将构建出来的新OkHttpClient对象传入到OkHttpGlideUrlLoader.Factory中即可。

现在自定义的拦截器已经启用了,可以开始去实现下载进度监听的具体逻辑了。首先新建一个ProgressListener接口,用于作为进度监听回调的工具,如下所示:

public interface ProgressListener {

    void onProgress(int progress);

}

然后在ProgressInterceptor中加入注册下载监听和取消注册下载监听的方法。修改ProgressInterceptor中的代码,如下所示:

public class ProgressInterceptor implements Interceptor { 

    static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();

    public static void addListener(String url, ProgressListener listener) {
        LISTENER_MAP.put(url, listener); 
    } 

    public static void removeListener(String url) { 
        LISTENER_MAP.remove(url); 
    } 

    @Override 
    public Response intercept(Chain chain) throws IOException { 
        Request request = chain.request(); 
        Response response = chain.proceed(request); 
        return response; 
    } 

}

使用了一个Map来保存注册的监听器,Map的键是一个URL地址。之所以要这么做,可能会使用Glide同时加载很多张图片,而这种情况下,必须要能区分出来每个下载进度的回调到底是对应哪个图片URL地址的。

下载进度的具体计算。需要新建一个ProgressResponseBody类,并让它继承自OkHttp的ResponseBody,然后在这个类当中去编写具体的监听下载进度的逻辑,代码如下所示:

public class ProgressResponseBody extends ResponseBody {

    private static final String TAG = "ProgressResponseBody";

    private BufferedSource bufferedSource;

    private ResponseBody responseBody;

    private ProgressListener listener;

    public ProgressResponseBody(String url, ResponseBody responseBody) {
        this.responseBody = responseBody;
        listener = ProgressInterceptor.LISTENER_MAP.get(url);
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override 
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
        }
        return bufferedSource;
    }

    private class ProgressSource extends ForwardingSource {

        long totalBytesRead = 0;

        int currentProgress;

        ProgressSource(Source source) {
            super(source);
        }

        @Override 
        public long read(Buffer sink, long byteCount) throws IOException {
            long bytesRead = super.read(sink, byteCount);
            long fullLength = responseBody.contentLength();
            if (bytesRead == -1) {
                totalBytesRead = fullLength;
            } else {
                totalBytesRead += bytesRead;
            }
            int progress = (int) (100f * totalBytesRead / fullLength);
            Log.d(TAG, "download progress is " + progress);
            if (listener != null && progress != currentProgress) {
                listener.onProgress(progress);
            }
            if (listener != null && totalBytesRead == fullLength) {
                listener = null;
            }
            currentProgress = progress;
            return bytesRead;
        }
    }

}

首先,定义了一个ProgressResponseBody的构造方法,该构造方法中要求传入一个url参数和一个ResponseBody参数。url参数就是图片的url地址了,而ResponseBody参数则是OkHttp拦截到的原始的ResponseBody对象。然后在构造方法中,调用了ProgressInterceptor中的LISTENER_MAP来去获取该url对应的监听器回调对象,有了这个对象,就可以回调计算出来的下载进度了。

由于继承了ResponseBody类之后一定要重写contentType()、contentLength()和source()这三个方法,在contentType()和contentLength()方法中直接就调用传入的原始ResponseBody的contentType()和contentLength()方法即可,这相当于一种委托模式。但是在source()方法中,就必须加入点自己的逻辑了,因为这里要涉及到具体的下载进度计算。

source()方法,先是调用了原始ResponseBody的source()方法来去获取Source对象,接下来将这个Source对象封装到了一个ProgressSource对象当中,最终再用Okio的buffer()方法封装成BufferedSource对象返回。

ProgressSource是一个自定义的继承自ForwardingSource的实现类。ForwardingSource也是一个使用委托模式的工具,它不处理任何具体的逻辑,只是负责将传入的原始Source对象进行中转。但是,使用ProgressSource继承自ForwardingSource,那么就可以在中转的过程中加入自己的逻辑了。

可以看到,在ProgressSource中重写了read()方法,然后在read()方法中获取该次读取到的字节数以及下载文件的总字节数,并进行一些简单的数学计算就能算出当前的下载进度了。这里先使用Log工具将算出的结果打印了一下,再通过前面获取到的回调监听器对象将结果进行回调。

计算下载进度的逻辑已经完成了,在拦截器当中使用它。修改ProgressInterceptor中的代码,如下所示:

public class ProgressInterceptor implements Interceptor { 
    ... 
    @Override 
    public Response intercept(Chain chain) throws IOException { 
        Request request = chain.request(); 
        Response response = chain.proceed(request); 
        String url = request.url().toString(); 
        ResponseBody body = response.body(); 
        Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
        return newResponse; 
    } 
}

也是一些OkHttp的简单用法。通过Response的newBuilder()方法来创建一个新的Response对象,并把它的body替换成刚才实现的ProgressResponseBody,最终将新的Response对象进行返回,这样计算下载进度的逻辑就能生效了。

修改activity_main.xml中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical"> 

    <Button 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:text="Load Image" 
        android:onClick="loadImage" 
        /> 

    <ImageView 
        android:id="@+id/image" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" /> 
</LinearLayout>

MainActivity

public class MainActivity extends AppCompatActivity {
    String url = "https://pic.netbian.com/uploads/allimg/240420/003439-17135444792cad.jpg";
    ImageView image;
    ProgressDialog progressDialog;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        image = (ImageView) findViewById(R.id.image);
        progressDialog = new ProgressDialog(this);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setMessage("加载中"); 
    }

    public void loadImage(View view) {
        ProgressInterceptor.addListener(url, new ProgressListener() {
            @Override
            public void onProgress(int progress) {
                progressDialog.setProgress(progress);
            }
        });
        Glide.with(this)
             .load(url)
             .diskCacheStrategy(DiskCacheStrategy.NONE)
             .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
             .into(new GlideDrawableImageViewTarget(image) {
                 @Override
                 public void onLoadStarted(Drawable placeholder) {
                     super.onLoadStarted(placeholder);
                     progressDialog.show();
                 }

                 @Override 
                 public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
                     super.onResourceReady(resource, animation);
                     progressDialog.dismiss();
                     ProgressInterceptor.removeListener(url);
                 }
             });
    }

新增了一个ProgressDialog用来显示下载进度,然后在loadImage()方法中,调用了ProgressInterceptor.addListener()方法来去注册一个下载监听器,并在onProgress()回调方法中更新当前的下载进度。

最后,Glide的into()方法也做了修改,这次是into到了一个GlideDrawableImageViewTarget当中。重写了它的onLoadStarted()方法和onResourceReady()方法,从而实现当图片开始加载的时候显示进度对话框,当图片加载完成时关闭进度对话框的功能。

运行一下程序,效果如下图所示:

在这里插入图片描述

六、Glide 4

Glide 4的用法相对于Glide 3其实改动并不大

6.1 依赖导入:

dependencies {
    implementation 'com.github.bumptech.glide:glide:4.4.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.4.0'
}

6.2 加载图片

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

和glide3没什么变化

6.3 占位图

在Glide的三步走之间加入一个apply()方法,来应用创建的RequestOptions对象。

RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.loading);
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);
RequestOptions options = new RequestOptions()
        .placeholder(R.drawable.ic_launcher_background)
        .error(R.drawable.error)
        .diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);

Glide 4中引入了一个RequestOptions对象,将这一系列的API都移动到了RequestOptions当中。好处是可以脱冗长的Glide加载语句,而且还能进行自己的API封装,因为RequestOptions是可以作为参数传入到方法中的。

可以写出这样的Glide加载工具类:

public class GlideUtil {
    public static void load(Context context,
                            String url,
                            ImageView imageView,
                            RequestOptions options) {
        Glide.with(context)
             .load(url)
             .apply(options)
             .into(imageView);
    }
}

6.4 指定图片大小

RequestOptions options = new RequestOptions()
        .override(200, 100);
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);

和glide3没什么变化

6.5 缓存机制

DiskCacheStrategy.NONE: 表示不缓存任何内容。
DiskCacheStrategy.DATA: 表示只缓存原始图片。
DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片。
DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。

其中,DiskCacheStrategy.DATA对应Glide 3中的DiskCacheStrategy.SOURCE,DiskCacheStrategy.RESOURCE对应Glide 3中的DiskCacheStrategy.RESULT。而DiskCacheStrategy.AUTOMATIC是Glide 4中新增的一种缓存策略,并且在不指定diskCacheStrategy的情况下默认使用就是的这种缓存策略。
其他几乎没什么变化。

6.6 指定加载格式

Glide.with(this)
     .asBitmap()
     .load("http://guolin.tech/test.gif")
     .into(imageView);

with()方法的后面加入了asBitmap()方法,只允许加载静态图片,不需要Glide自动判断了。传入是一张GIF图的话,Glide会展示这张GIF图的第一帧,而不会去播放它。

在Glide 3中的语法是先load()再asBitmap()的,而在Glide 4中是先asBitmap()再load()的。

也能强制指定加载动态图片,对应的方法是asGif()。而Glide 4中又新增了asFile()方法和asDrawable()方法,分别用于强制指定文件格式的加载和Drawable格式的加载。

6.7 自定义全局Glide

导入依赖:

    //glide4
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'

自定义类,名称最好就按这个来

@com.bumptech.glide.annotation.GlideModule
public class GlideModule extends AppGlideModule {

    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {

        // 设置默认的加载选项,包括缓存策略为 DiskCacheStrategy.RESOURCE
        // 设置默认的加载选项,如占位符、错误占位符等
        builder.setDefaultRequestOptions(new RequestOptions()
                .placeholder(R.drawable.android)
                .error(R.drawable.error)
                .diskCacheStrategy(DiskCacheStrategy.RESOURCE));

        // 设置内存缓存大小
        builder.setMemoryCache(new LruResourceCache(10 * 1024 * 1024)); // 10MB

        // 设置磁盘缓存大小和位置
//        builder.setDiskCache(new InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024)); // 50MB
    }

    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
        super.registerComponents(context, glide, registry);
    }
    @Override
    public boolean isManifestParsingEnabled() {
        return false;
    }
}

manifest添加

        <meta-data
            android:name="com.example.glide.GlideModule"
            android:value="AppGlideModule" />

然后make以下,直到能够正常使用GlideApp这个类就成功了

      GlideApp.with(holder.itemView)
                                .load(resourceId)
                                .apply(RequestOptions.bitmapTransform(new RoundedCorners(20)))
                                .into(holder.imageView);

其他地方大同小异

七、Glide和Picasso的对比

该对比源自链接Google推荐的图片加载库Glide介绍,可能有落后的地方,后续改进。

对比项glidepicasso
导入方式compile ‘com.github.bumptech.glide:glide:3.5.2’ compile ‘com.android.support:support-v4:22.0.0’compile ‘com.squareup.picasso:picasso:2.5.1’
基础使用Picasso.with(context),建议传参的时候传递Activity 和 Fragment给GlidePicasso.get()
默认Bitmap格式RGB_565ARGB_8888
默认内存开销比较Glide加载的大小和ImageView的大小是一致的,因此更小Picasso加载了全尺寸的图片到内存,开销较大
Image质量的细节Glide加载的图片没有Picasso那么平滑
磁盘缓存Glide会为每种大小的ImageView缓存 一次。通过.diskCacheStrategy(DiskCacheStrategy.ALL)让Glide既缓存全尺寸又缓存其他尺寸Picasso只缓存一个全尺寸的
磁盘缓存结论Glide的这种方式优点是加载显示非常快,Glide远比Picasso快,虽然需要更大的空间来缓存。Picasso的方式因为需要在显示之前重新调整大小而导致一些延迟
Image Resizing// Picasso.resize(300, 200);// Glide.override(300, 200);
Center Cropping// Picasso.centerCrop();// Glide.centerCrop();
Transforming// Picasso.transform(new CircleTransform())// Glide.transform(new CircleTransform(context))
设置占位图或者加载错误图//Picasso.placeholder(R.drawable.placeholder).error(R.drawable.imagenotfound)Glide.placeholder(R.drawable.placeholder).error(R.drawable.imagenotfound)
Glide可以做而Picasso 做不到Glide可以加载GIF动态图,但会消费太多的内存,谨慎使用。Glide可以配置图片显示的动画,Picasso不能加载GIF动态图,Picasso只有一种动画:fading in。
总结对比Glide加载图像以及磁盘缓存的方式都要优于Picasso,速度更快,并且Glide更有利于减少OutOfMemoryError的发生,GIF动画是Glide的杀手锏。Picasso的图片质量更高

建议使用Glide,将Bitmap格式换成 ARGB_8888、让Glide缓存同时缓存全尺寸和改变尺寸两种。

参考链接:
Android图片加载框架最全解析(八),带你全面了解Glide 4的用法
GlideModule过时使用AppGlideModule
Glide 4.11 异常
Glide 新版中GlideApp(AppGlideModule)配置方法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值