Android图片加载(二)——Glide框架的引入和使用指南

前言

上一讲 Android图片加载(一)——框架的对比分析 中我们提到了列表控件在快速滑动或者是网络不好的时候,可能会出现图片错位、重复、闪烁等问题,为了使用户拥有良好的App使用体验,不仅要快速加载图片,而且还不能有过多的主线程I/O或频繁的垃圾回收。因此,我们引入了图片加载框架Glide。

Glide是一个被google所推荐的图片加载库,作者是bumptech。这个库被广泛运用在google的开源项目中,包括2014年的google I/O大会上发布的官方app。
其特点包括但不限于:

  • 使用简明的流式语法API,它允许你在大部分情况下一行代码搞定需求。
      Glide.with(fragment)
        .load(url)
        .into(imageView);
    
  • 性能好,充分考虑了Android图片加载性能的两个关键方面:图片解码的速度、解码图片带来的资源压力。
  • 可以自动、智能地下采样和缓存, 以最小化存储开销和解码次数。能够积极的资源重用,例如字节数组和Bitmap对象,以最小化垃圾回收和堆碎片的影响。包含深度的生命周期集成,以确保仅优先处理活跃的Fragment和Activity的请求,有利于应用在必要时释放资源以避免在后台时被杀掉。
  • 2. 使用

    1. 添加gradle依赖
       dependencies {
         implementation 'com.github.bumptech.glide:glide:4.11.0'
         annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
    
    2. 基本方法

    例如加载一个地址为url的图片到uImageView中:

    Glide.with(uContext).load(url).into(uImageView);
    

    其中,uContext是上下文,这里还可以使用Activity、FragmentActivity、androidx.Fragment.app.Fragment等对象。with方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止。
    此外,也可以通过clear取消不必要的加载,但是这并不是必须的操作,实际上,当Glide.with()中传入的Activity或者是Fragment实例被销毁时,Glide会自动取消加载并回收资源。

    3. 占位图
  • 顾名思义,占位图就是指在图片的加载过程中,我们先显示一张临时的图片,等图片加载出来了之后再替换成要加载的图片。
  • Glide.with(this)
         .load(url)
         .placeholder(R.drawable.loading) //
         .into(imageView);
    
  • 除了加载占位图之外,还有一种异常占位图。异常占位图是指,如果因为某些异常情况导致图片加载失败,比如说手机网络信号不好,这个时候就显示这张异常占位图。
    一般通过串接error()方法来指定异常占位图。
  • null占位图
    正常情况下,我们的 load() 方法里面的 url 应该不是 null 的,但是如果有可能为 null 的情况,你可以通过设置 fallback() 方法来显示 url 为 null 的情况。
  • 4. 加载指定大小的图片

    我们平时在加载图片的时候很容易会造成内存浪费。比如说一张图片的尺寸是1000X1000像素,但是我们界面上的ImageView可能只有200X200像素,这个时候如果你不对图片进行任何压缩就直接读取到内存中,这就属于内存浪费了,因为程序中根本就用不到这么高像素的图片。
    而使用Glide,我们完全不用担心图片内存浪费,甚至是内存溢出的问题。因为Glide从来都不会直接将图片的完整尺寸全部加载到内存中,而是用多少加载多少。Glide会自动判断ImageView的大小,然后只将这么大的图片像素加载到内存当中(默认情况下),帮助我们节省内存开支。所以使用Glide,在绝大多数情况下我们都是不需要指定图片大小的,因为Glide会自动根据ImageView的大小来指定图片的大小。
    不过Glide仍然支持给图片指定一个固定的大小的功能(使用override),指定后不管imagview的大小是多少,Glide只会讲图片加载成设定的尺寸。此外,如果不想让Glide计算并压缩要加载的图片,要加载原始图片的大小,可以使用override(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL)作为options添加上去,但是这种加载方式是不被建议的,因为占用的内存会相差很大。

    5. 加载不同格式:Bitmap、Gif、Drawable、File

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

  • asDrawable() 得到 RequestBuilders
  • asGif() 得到 RequestBuilders
  • asBitmap() 得到 RequestBuilders
  • asFile() 得到 RequestBuilders
    默认情况下,如果我们不指定,则是得到一个 RequestBuilders 对象。
    例如,代码中使用:
  • Glide.with(this)
            .asBitmap()
            .load(url)
            .into(userAvatarImageView);
    

    表示加载bitmap

    6. 换一种方式加载图片

    正常情况下,我们都是通过这行代码来加载图片的:

    Glide.with(uContext).load(url).into(uImageView);
    

    但是有时候,为了对加载出来的图像进行一些处理,我们还可以使用Glide为我们提供的一个封装,封装成一个Target或者Target的子类,例如SimpleTarget。

    Glide.with(this)
         .asBitmap()
         .load(loginInfo.getAvatarUrl())
         .into(new SimpleTarget<Bitmap>() {
          @Override
     public void onResourceReady(Bitmap resource, Transition<? super Bitmap>  transition) {
           userAvatarImageView.setImageBitmap(resource);
           LoginUser.bitmap=resource;
    

    我们使用SimpleTarget可以将Glide加载出来的图片对象获取到并做一些处理,而不是像之前那样只能显示图片。比如说,这里获取到加载出来的图片对象之后不仅将它显示到一个ImageView当中,还将它存储到LoginUser.bitmap中作为一个静态变量,方便后续页面的使用。

    7. 使用Generated API

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

  • 集成库可以为Generated API扩展自定义选项。
  • 在Appplication模块中可以将常用的选项组打包成一个选项在Gnenrated API中使用。
  • 我在代码中引入了Generated API主要出于目的2,虽然可以通过手动创建RequestOptions子类的方式来完成,但是这种用法降低了API使用的流畅性,如果使用 Generated API ,可以使我们不用去创建 RequestOptions 就可以直接使用 placeholder、error 等方法。简单理解一下就是增加代码复用行,便于后续的代码维护。
    下面通过实际的代码来展示Generated API的使用:

  • 使用Generated API必须首先引入下列依赖:
  • annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
    
  • 在Application模块下创建一个新的类继承自GlideModule,并给类添加@GlideModule注解。
  • @GlideModule
    public class MyAppGlideModule extends AppGlideModule {
                @Override
            public void applyOptions(Context context, GlideBuilder builder)        {
                builder.setDefaultRequestOptions(
                        new RequestOptions()
                           .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
                           .placeholder(R.drawable.logo)
                           .fallback(R.drawable.fail)
                           .format(DecodeFormat.PREFER_ARGB_8888));
    

    例如,上面的MyAppGlideModule中我们使用applyOptions的方法配置了一些options,Generated API默认名字为GlideApp, 把原来的Glide.with替换成GlideApp.with,就可以使用该API去完成加载工作。

    8. 集成网络框架

    Glide默认使用的是HTTPUrlConnection,支持集成Volley,Okhttp等网络栈。
    为了使用比Android提供的HttpUrlConnection更nice的API,以及想要确保网络层代码不依赖于app所在的设备上Android OS版本,所以选择Okhttp。其次,我们已经在获取远程github数据的时候使用了Okhttp,那么我们继续选择为Glide使用OKhttp。

    添加一个对OKhttp集成库的依赖:

    compile "com.github.bumptech.glide:okhttp3-integration:4.11.0" 
    

    添加Okhttp集成库的Gradle依赖将使Glide自动开始使用Okhttp来加载所有来自http和httpsURL的图片。

    9. 图片的缓存处理

    Glide的缓存包含内存缓存和硬盘缓存。默认情况下,Glide自动就是开启内存缓存和磁盘缓存的。内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,而硬盘缓存的主要作用是防止应用从网络或其他地方重复下载和读取数据。(??这两句话难道不是一个意思吗)
    9.1 内存缓存

    默认情况下,Glide自动就是开启内存缓存的。也就是说,当我们使用Glide加载了一张图片之后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除之前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。比方说你在一个RecyclerView当中反复上下滑动,RecyclerView中只要是Glide加载过的图片都可以直接从内存当中迅速读取并展示出来,从而大大提升了用户体验。还可以使用skipMemoryCache()方法并传入true,就表示禁用掉Glide的内存缓存功能。
  • 从内存缓存中读取图片的逻辑大致是:如果能从内存缓存当中读取到要加载的图片,那么就直接进行回调,如果读取不到的话,才会开启线程执行后面的图片加载逻辑。
  • Glide的图片加载过程会调用两个方法来获取内存缓存,LoadFromCache()和LoadFromActiveResource()。这两个方法中一个使用的就是LruCache算法,另一个使用的就是弱引用。activeResources就是一个弱引用的HashMap,用来缓存正在使用中的图片,使用activeResources来缓存正在使用中的图片,可以保护这些图片不会被LruCache算法回收掉。总结:正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存。
  • 9.2 硬盘缓存

  • 当我们使用Glide去加载一张图片的时候,Glide默认并不会将原始图片展示出来,而是会对图片进行压缩和转换。Glide默认情况下在硬盘缓存的就是转换过后的图片。
  • 可以通过.diskCacheStrategy来设置磁盘的缓存策略
    包括下面这些:
  •   //DiskCacheStrategy.ALL 既缓存原始图片,也缓存转换过后的图片。
      // DiskCacheStrategy.NONE 不缓存任何内容
      // DiskCacheStrategy.DATA 在资源解码前就将原始数据写入磁盘缓存(即只缓存原始图片)
      // DiskCacheStrategy.RESOURCE 在资源解码后将数据写入磁盘缓存,即经过缩放等转换后的图片资源(即只缓存转换过后的图片)。
      // DiskCacheStrategy.AUTOMATIC 让Glide根据图片资源智能地选择使用哪一种缓存策略。
      //默认采用DiskCacheStrategy.AUTOMATIC策略
      /*-------------------------------------------------------------------------------*/
      //源码 RequestOptions.java
      private DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.AUTOMATIC;
    

    三、从源码的角度理解Glide的执行流程

    上面介绍的使用过程中实际上有小部分已经讨论到了Glide的源码,但是由于Glide的源码比较复杂,对于笔者这种菜鸡来说是比较难以自己下手的,这里引用一下《第一行代码》作者郭霖大神的原话:

    阅读源码到底困难吗?这个当然主要还是要视具体的源码而定。比如同样是图片加载框架,我读Volley的源码时就感觉酣畅淋漓,并且对Volley的架构设计和代码质量深感佩服。读Glide的源码时却让我相当痛苦,代码极其难懂。当然这里我并不是说Glide的代码写得不好,只是因为Glide和复杂程度和Volley完全不是在一个量级上的。

    但是郭霖大神仍然给出了一套更容易被理解的源码解析,感兴趣的同学可以去看:
    Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程