Android开发经验谈Android开发Android开发

Glide V4使用指南

2018-10-26  本文已影响395人  MrTrying

Glide的强大和灵活相信不需要多介绍了

本文使用Glide版本为4.8.0,因为使用的Java语言进行开发,涉及到使用Kotlin的部分还请参考官方文档

SDK要求

一、集成

在项目的build.gradle文件中添加google()仓库

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

在要使用的Glidemodule中添加以下代码

implementation('com.github.bumptech.glide:glide:4.8.0')
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
//如果使用了在Kotlin中使用了Glide注解,需要引入kapt依赖代替annotationProcessor依赖
//kapt 'com.github.bumptech.glide:compiler:4.8.0'

若使用了不是27的SupportLibrary版本,使用以下代码

implementation('com.github.bumptech.glide:glide:4.8.0') {
    exclude group: "com.android.support"
}
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'

使用了不同版本的SupportLibrary可能导致一些运行时异常,

java.lang.NoSuchMethodError: No static method getFont(Landroid/content/Context;ILandroid/util/TypedValue;ILandroid/widget/TextView;)Landroid/graphics/Typeface; in class Landroid/support/v4/content/res/ResourcesCompat; or its super classes (declaration of 'android.support.v4.content.res.ResourcesCompat' 
at android.support.v7.widget.TintTypedArray.getFont(TintTypedArray.java:119)

尽量避免使用@aar,如果需要这么做,需要添加transitive=true确保有所需要的类。

dependencies {
    implementation ("com.github.bumptech.glide:glide:4.8.0@aar") {
        transitive = true
    }
}

@aarGradle的限制符,默认会去除使用到得依赖。如果不添加transitive=true的话将会导致运行时异常。

java.lang.NoClassDefFoundError: com.bumptech.glide.load.resource.gif.GifBitmapProvider
    at com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder.<init>(ByteBufferGifDecoder.java:68)
    at com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder.<init>(ByteBufferGifDecoder.java:54)
    at com.bumptech.glide.Glide.<init>(Glide.java:327)
    at com.bumptech.glide.GlideBuilder.build(GlideBuilder.java:445)
    at com.bumptech.glide.Glide.initializeGlide(Glide.java:257)
    at com.bumptech.glide.Glide.initializeGlide(Glide.java:212)
    at com.bumptech.glide.Glide.checkAndInitializeGlide(Glide.java:176)
    at com.bumptech.glide.Glide.get(Glide.java:160)
    at com.bumptech.glide.Glide.getRetriever(Glide.java:612)
    at com.bumptech.glide.Glide.with(Glide.java:684)

权限

这个不需要多说,如果加载网络图片必然需要网络权限,这个还是根据具体的使用情况而定。

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

Glide添加了链接监听(Connectivity Monitoring),通过网络加载图片时,Glide可以监听链接状态并在重新链接到网络时重启之前失败的请求。使用链接监听我们需要添加ACCESS_NETWORK_STATE权限,Glide将自动监听连接状态,不需要额外的改动。

如果需要使用ExternalPreferredCacheDiskCacheFactory将Glide的缓存存放到SD卡上,还需要添加READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限。

混淆

如果使用了混淆,需要添加以下的代码到混淆的配置文件中

-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}

如果使用的target API低于27的话还需要添加

-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder

VideoDecoder使用API 27的一些借口,可能导致混淆发出警告。

如果使用了DexGuard,可能还需要添加

-keepresourcexmlelements manifest/application/meta-data@value=GlideModule

二、简单使用

V4的基本用法还是没变,一句简单地链式调用就能讲图片加载到ImageView上了

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

这里context就是一个Context对象,而url是一个网络图片链接,imageView则是需要显示图片的ImageView。V4还可以手动取消加载,例如:

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

Glide.with()中传入的ActivityFragment销毁时,Glide会自动取消加载并回收资源
这里会提出clear方法,在RecyclerView中复用View来加载图片,每次需要调用Glide重新进行加载操作或者使用clear方法停止之前的请求,避免图片显示错乱的问题。

WX20181022-232358@2x.png

可以看到with方法还是很强大,支持ContextActivityFragmentView等,只是将原本支持传入android.app.Fragment的方法标记为过期了。

WX20181022-232555@2x.png

load方法也提供了强大的支持,Uri、文件、btye[]、网络连接、资源id、bitmap、drawable都可以加载。

Generated API(生成API)

Glide V4使用注解处理器生成一个流式API,用于RequestBuilderRequestOptions等相关的所有选项。Glide的文档中有说明,其目的一是为了更好地扩展自定义选项,其二是为了方便打包常用选项组。

RequestOptions的设置,Glide V4还提供了apply方法设置单次请求的选项,以及applyDefaultRequestOptions设置默认请求选项。

从Glide V3到Glide V4的升级,生成API是一个很大的改变,接下来详细说明。

生成API目前只能在Application模块中使用,无法同时在各个LibraryApplication中定义各自的生成API,确保了调用API时请求的选项是一致的。官方也指出,在将来的版本中可能解除此限制。

使用Generated API仅仅需要两步,第一步,在build.gradle的依赖中添加annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'上面其实已经给出;第二步,我们需要创建一个class继承AppGlideModule,并为该类添加@GlideModule注解。

package com.mrtrying.glidev4_example;

import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;

GlideModule
public final class ExampleAppGlideModule extends AppGlideModule {}

AppGlideModule虽然是抽象类,却可以不用重写任何方法。但必须使用@GlideModule注解标记该类,否则没法顺利的生成GlideAppAPI

PS:第一次添加AppGlideModule或者对AppGlideModule做了某些修改时,我们需要重新构建项目重新生成API。如果AndroidStudio没法自动完成构建,可以使用Build—->Rebuild Project手动重新构建,必要时可能需要手动删除modulebuild目录再重新构建项目。

生成API的默认名为GlideApp,其包名与所在的module的包名相同,而基本用法也于之前相同,只是将Glide替换成了GlideApp,就可以使用了

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

Glide生成API还有GlideExtensionGlideOptionGlideType,暂时只对AppGlideModule进行说明,其他的涉及到较深度的使用,放在后面说明。

占位符

Glide提供了三种不同类型的占位符:

GlideApp.with(this)
        .load(url)
        .placeholder(R.mipmap.placeholder)
        .error(R.mipmap.error)
        .fallback(R.mipmap.fallback)
        .into(imageView);

先说API的参数,placeholdererrorfallback相同可以传入资源id或者Drawable对象。

接下来说说这三个占位符的调用时机,用过V3的同学一定知道placeholer占位符是当资源正在请求是被展示的图片或者资源文件,当请求成功时占位符会被替换成请求到得资源;error则是在请求永久性失败是展示;fallback是当请求的url(或者model)为null时展示,此占位符的目的是设置null是否是可接收的正常情况,而Glide将null作为错误处理。

三个占位符对应了三种时机,其中还有一些状态,无可避免的会出现一些复杂的情况需要说明:

占位符这个功能确实很棒,有朋友不经要问了,如果我想加载网络图片怎么办?这个就十分抱歉了,Glide并不提供这样的功能,只能支持资源文件或者是Drawable对象。当然,你也可以提前下载要网络图片在以Drawable形式加载,只要这样的效率你可以接收(个人觉得没有什么必要)。

官方文档在占位符最后的FAQ中有说明,占位符是在主线程中,从Android Resource加载的,而且Glide的Transformation不会被应用到占位符上。这都是希望在使用占位符时能尽量少得占用系统资源来考虑的,所以Glide并没有提供他们认为不太合理的API。

关于图片的变换,比较常用到得是圆角和模糊,圆角的处理的话比较推荐RCLayout,虽然在背景的处理上会有一点锯齿,不过还是很不错的一个库;而模糊的话推荐使用android自带的RenderScript,相关方法在API 17以上提供,也提供了相关的support包。

transformation(变换)

Glide提供了transformation得功能,在获取到请求的图片之后,能对图片进行一些处理,例如:裁剪、模糊等;而transformation的强大在于可以自定义,这样一来transformation不仅能处理bitmap,同样可以用于处理GIF动画,还有自定义资源类型。

Glide在API上提供的了5个相关的方法可以直接使用

optionalFitCenter()的具体作用没有太清楚,效果与fitCenter()相同,有知道的大佬请告知

使用方式基本和占位符一致,在load()方法后调用相关方法就能使用相应的transformation效果了

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

这是在生成API中的使用,在Glide普通的API也是可以使用的,不过没有直接可以调用的方法,需要通过RequestOptions来设置

RequestOptions options = new RequestOptions().centerCrop();

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

除了可以自己创建以外,Glide提供了静态获取RequestOptions方法,可以直接使用

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

针对ImageView可能自身也会设置scaleType的情况,Glide在部分情况会自动应用 FitCenterCenterCrop,如果 scaleTypeCENTER_CROP , Glide 将会自动应用 CenterCrop 变换。如果 scaleTypeFIT_CENTERCENTER_INSIDEGlide会自动使用 FitCenter 变换。
当然,设置了其他的变换也可以将其覆盖;或者使用dontTransform()方法就不会在执行任何变换。

这里需要注意的是,Glide内置的这几种transformation,即使在使用多种变换,也只有最后一个transformation会生效。这个不仅仅是对Glide内置的transformation也包括transform()设置的transformation,都会替换掉之前的transformation

如果需要支持多种变换,需要使用transform()设置MultiTransformation类对象传入,MultiTransformation的构造器可以接收可变参数或者Transformation的集合;或者在transforms()方法中设置多个(不要以为是一个方法,这是transforms(),结尾有s的)而这些个变化的应用顺序就是传入参数的顺序。

GlideApp.with(this)
        .load(url)
        .transform(new MultiTransformation<>(new FitCenter(),new RoundedCorners(3)))
        .into(imageView);
//或者
GlideApp.with(this)
        .load(url)
        .transforms(new FitCenter(),new RoundedCorners(3))
        .into(imageView);

由于Transformation是没有状态的,我们可以再多个加载中复用同一个Transformation对象。

自定义transformation

为了方便使用,我们希望一些特殊的变换也能像上面一样的使用,这种情况可以通过实现Transformation来处理。

这里直接以Glide内置实现的RoundedCorners为例

public final class RoundedCorners extends BitmapTransformation {
  private static final String ID = "com.bumptech.glide.load.resource.bitmap.RoundedCorners";
  private static final byte[] ID_BYTES = ID.getBytes(CHARSET);

  private final int roundingRadius;

  /**
   * @param roundingRadius the corner radius (in device-specific pixels).
   * @throws IllegalArgumentException if rounding radius is 0 or less.
   */
  public RoundedCorners(int roundingRadius) {
    Preconditions.checkArgument(roundingRadius > 0, "roundingRadius must be greater than 0.");
    this.roundingRadius = roundingRadius;
  }

  @Override
  protected Bitmap transform(
      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
    return TransformationUtils.roundedCorners(pool, toTransform, roundingRadius);
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof RoundedCorners) {
      RoundedCorners other = (RoundedCorners) o;
      return roundingRadius == other.roundingRadius;
    }
    return false;
  }

  @Override
  public int hashCode() {
    return Util.hashCode(ID.hashCode(),
        Util.hashCode(roundingRadius));
  }

  @Override
  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
    messageDigest.update(ID_BYTES);

    byte[] radiusData = ByteBuffer.allocate(4).putInt(roundingRadius).array();
    messageDigest.update(radiusData);
  }
}

可以看到RoundedCorners是继承自BitmapTransformationBitmapTransformation已经帮我们处理了部分基础逻辑,比如提取和回收原始的Bitmap,而我们处理好Bitmap的变换就可以了。

除了自身的构造函数意外还重写了4个方法,其中transform方法就是进行变换处理的方法,变换完成之后返回变换完成的Bitmap。其余的三个方法,updateDiskCacheKey是必须实现的,而为例保证内存和磁盘缓存正常,equals()hashCode()也是必须重写的。

RoundedCorners使用完整报名路径的限定名来作为一个 ID,它可以构成 hashCode() 的基础,并可用于更新 updateDiskCacheKey() 传入的 MessageDigest。如果你的 Transformation 需要参数而且它会影响到 Bitmap 被变换的方式,它们也必须被包含到这三个方法中,就像RoundedCorners一样。原来的ID保留,但roundingRadius也包含到了这个三个方法中,updateDiskCacheKey 方法还演示了你可以如何使用 ByteBuffer 来包含基本参数到你的 updateDiskCacheKey 实现中。

官方文档着重指出必须要重写equals()hashCode()方法,虽然不重写不会出现编译问题,但是这不代表能正常工作。

三、进阶

Target

或许在使用into()时有些印象,into()方法可以直接传入ImageView也可以使用Target。其实跟踪一下Glide的源码就会发现其实在传入ImageView时,最终也在ImageViewTargetFactory的包装下返回了ImageViewTarget,而ViewTarget的最上层的父类就是Target

into()方法不仅用于启动每一个请求,同时也制定了接收请求结果的Target

Target<Drawable> target = GlideApp.with(this)
        .load(url)
        .centerCrop()
        .into(new Target<Drawable>() {
            //Target的方法太多,这里代码省略...
        });

如果重用同一个Target对加载一个新的请求,那么之前的的请求都会被取消并且释放资源。我们也可以使用clear()方法对不需要重新加载的请求进行相关资源的释放。

GlideApp.with(this).clear(target);

上面的情况中直接使用ImageVIew也是可以的。因为ViewTarget使用了setTag()getTag()存储了Request,所以可以直接从Viewtag取回之前一次加载的信息,也是是ImageVIew的默认tag被占用的原因。

也就是说,在使用ImageVIew的情况中,即使使用同一个ImageView重新加载也是可以释放之前的请求和资源的

Glide.with(this)
        .load(url)
        .into(imageView);
//加载新连接
Glide.with(this)
        .load(newUrl)
        .into(imageView);

只要继承ViewTarget或者重写setRequest()getRequest()并实现取回上一次加载的信息,重用的机制就可以得以保证。

Transitions(过渡)

Glide V3和V4不同,不会默认应用交叉淡入或任何其他的过渡效果,每个请求需要手动应用过渡。Glide提供了很多过渡动画,我们可以手动应用于每一个请求上;内置过渡的运行方式是一致的,会根据加载图像不同的情况来决定是否执行过渡。例如:如果Glide从内存缓存中加载出来,Glide的内置过渡将不会执行;而加载磁盘缓存、本地文件或者远程连接时都会执行Glide的内置过渡。

可以通过transition()方法设置TransitionOptions

Glide.with(this)
    .load(url)
    .transition(DrawableTransitionOptions.withCrossFade())
    .into(imageView);

TransitionOptions用于给一个特定的请求指定过渡,而不同的资源类型能决定使用什么类型的过渡选项。Bitmap 和 Drawable可以对应使用使用 BitmapTransitionOptionsDrawableTransitionOptions 来指定类型特定的过渡动画。对于 BitmapDrawable 之外的资源类型,可以使用 GenericTransitionOptions

如果需要自定义过渡动画,我们需要通过DrawableTransitionOptions.with()生成我们自己的TransitionOptions,而with()需要出入一个TransitionFactory对象。TransitionFactory是一个接口我们需要实现一个这样的类

public class ExampleTransitionFactory implements TransitionFactory {
    @Override
    public Transition build(DataSource dataSource, boolean isFirstResource) {
        return null;
    }
}

通过DrawableTransitionOptions.with(new ExampleTransitionFactory())我们就能调用transition()方法来加载我们的定制的过渡动画。transition()方法支持动画的资源id,AnimatorTransitionFactory,可以通过这三种方式来实现动画部分。

最后就是执行transition()来加载我们自定义的动画

GlideApp.with(this)
        .load(url)
        .transition(DrawableTransitionOptions.with(new ExampleTransitionFactory()).transition(R.anim.show))
        .into(imageView);

这里需要提醒的是,动画对于性能的开销不用多说,比图片解码本身还要耗时,在列表的快速滑动的情况下可能造成加载缓慢。在列表中考虑是否使用动画,在一些希望图片尽快加载出来的时候也需要做此考虑。

未完待续...

上一篇下一篇

猜你喜欢

热点阅读