Android 图片加载(三)图片加载框架Glide 进阶篇

原文链接:http://bumptech.github.io/glide/
Github地址:https://github.com/bumptech/glide

上一篇:Android 图片加载(二)图片加载框架Glide 入门篇


一、变换

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

Glide 提供了很多内置的变换,包括:

  • CenterCrop
  • FitCenter
  • CircleCrop

1. 默认变换

我们可以使用多种方式实现变换:

  • 直接使用RequestOptions类
RequestOptions requestOptions = new RequestOptions()
        .centerCrop();  
Glide.with(this)
        .load(IMAGE_URL)
        .apply(requestOptions)   //通过requestOptions设置变换
        .into(imageView);
  • RequestOptions类的静态方法
Glide.with(this)
        .load(IMAGE_URL)
        .apply(RequestOptions.centerInsideTransform())  //静态方法设置变换
        .into(imageView);
  • 内联方法
Glide.with(this)
        .load(IMAGE_URL)
        .fitCenter()   //内联方法设置变换
        .into(imageView);

2. 多重变换

默认情况下,每个 transform() 调用,或任何特定转换方法(fitCenter(), centerCrop(), bitmapTransform())的调用都会替换掉之前的变换。如果你想在单次加载中应用多个变换,请使用 MultiTransformation 类。

Glide.with(this)
        .load(IMAGE_URL)
        .transform(new MultiTransformation<>(new CenterInside(), new Rotate(180)))  //设置多重变换
//        .transform(new CenterCrop(), new Rotate(90))  //效果等同于new MultiTransformation<>()
        .into(imageView);

MultiTransformation 构造器变换参数的顺序,决定了这些变换的应用顺序。

3. 自定义变换

尽管 Glide 提供了各种各样的内置 Transformation 实现,如果你需要额外的功能,你也可以实现你自己的Transformation

如果你只需要变换 Bitmap,最好是从继承 BitmapTransformation 开始。BitmapTransformation 为我们处理了一些基础的东西,例如,如果你的变换返回了一个新修改的 Bitmap ,BitmapTransformation将负责提取和回收原始的 Bitmap。

先看个简单的示例:

public class MyBitmapTransformation extends BitmapTransformation {

    private static final String ID = "test.android.com.testapp.bitmap.MyBitmapTransformation";
    private static byte[] ID_BYTES = null;

    static {
        try {
            ID_BYTES = ID.getBytes(STRING_CHARSET_NAME);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
        if (toTransform.getHeight() == outHeight && toTransform.getWidth() == outWidth) {
            return toTransform;
        }
        return Bitmap.createScaledBitmap(toTransform, outWidth, outHeight, /*filter=*/ true);
    }

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

    @Override  
    public boolean equals(@Nullable Object obj) {
        return obj instanceof MyBitmapTransformation;
    }


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

请特别注意,对于任何Transformation子类,包括BitmapTransformation,下面列出的三个方法你都必须实现,以使得磁盘和内存缓存正确地工作

  1. equals()
  2. hashCode()
  3. updateDiskCacheKey

说明:即使你没有实现这三个方法,也能通过编译,但这可能导致自定义的Transformation无法正常工作。

如果你的 Transformation 没有参数,通常使用一个包含完整包限定名的 static final String 来作为一个 ID,它可以构成 hashCode() 的基础,并可用于更新 updateDiskCacheKey() 传入的 MessageDigest。如果你的Transformation有参数而且它会影响到Bitmap被变换的方式,它们也必须被包含到这三个方法中。

例如Glide提供的RoundedCorners变换,它含有一个圆角弧度参数roundingRadius,而且roundingRadius参与到了Bitmap的变换中,因此参数必须包含到三个方法中。RoundedCorners源码如下所示:

/**
 * A {@link BitmapTransformation} which rounds the corners of a bitmap.
 */
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);
  }
}

二、自定义模块

  • 添加对Glide 的注解解析器的依赖和对OkHttp集成库的依赖
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:4.8.0'
  • 创建自定义模块
  1. 实现AppGlideModule 类
  2. 给上述实现添加@GlideModule注解。
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        
    }

    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
        
    }
}

这样模块类就创建完成了。由于自定义模块类涉及的内容和应用场景比较复杂,可能会抽时间写一篇专门的文章详细介绍它,这里只是为接下来介绍【应用程序选项】做个简单的铺垫。

三、应用程序选项

Glide 允许应用通过模块类 AppGlideModule 实现来完全控制Glide 的内存和磁盘缓存使用。Glide 试图提供对大部分应用程序合理的默认选项,但对于部分应用,可能就需要定制这些值。在你做任何改变时,请注意测量其结果,避免出现性能的倒退。

1. 内存缓存

默认情况下,Glide使用 LruResourceCache ,这是 MemoryCache 接口的一个缺省实现,使用固定大小的内存和 LRU 算法。LruResourceCache的大小由 Glide 的 MemorySizeCalculator 类来决定,这个类主要关注设备的内存类型,设备 RAM 大小,以及屏幕分辨率。

应用程序可以自定义MemoryCache的大小,具体是在它们的AppGlideModule中使用 applyOptions(Context, GlideBuilder) 方法配置MemorySizeCalculator

@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        //1.根据设备信息计算出合适的内存缓存大小
        MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
                .setMemoryCacheScreens(2)
                .build();
        builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize()));
        //2.也可以直接覆盖缓存大小
        int memoryCacheSize = 1024*1024*15; //15M内存缓存
        builder.setMemoryCache(new LruResourceCache(memoryCacheSize));
        //3.提供自己的MemoryCache实现
        builder.setMemoryCache(new MemoryCache() {
            //省略方法实现
        });
    }
}

2. Bitmap池

Glide 使用 LruBitmapPool 作为默认的 BitmapPoolLruBitmapPool是一个内存中的固定大小的 BitmapPool,使用 LRU 算法清理。默认大小基于设备的分辨率和密度,同时也考虑内存类和 isLowRamDevice 的返回值。具体的计算通过 Glide 的MemorySizeCalculator来完成,与 Glide 的MemoryCache的大小检测方法相似。

BitmapPool配置方式与内存缓存相似:

@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        //1.根据设备信息计算出合适的BitmapPool缓存大小
        MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
                .setBitmapPoolScreens(4)
                .build();
        builder.setBitmapPool(new LruBitmapPool(calculator.getBitmapPoolSize()));
        //2.也可以直接覆盖BitmapPool大小
        int bitmapPoolSizeSize = 1024*1024*15; //15M内存缓存
        builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeSize));
        //3.提供自己的BitmapPool实现
        builder.setBitmapPool(new BitmapPool() {
            //省略方法实现
        });
    }
}

3. 磁盘缓存

Glide 使用 DiskLruCacheWrapper 作为默认的 磁盘缓存DiskLruCacheWrapper是一个使用 LRU 算法的固定大小的磁盘缓存。默认磁盘大小为 250 MB,位置是在应用的缓存文件夹中的一个 特定目录。

缓存位置可以是外部存储或者内部存储,我们还可以自己设置缓存大小、改变缓存文件夹在外存或内存上的名字:

@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        int diskCacheSize = 1024 * 1024 * 100;  //100MB 缓存大小
        String diskCacheFileName = "diskCacheFileName";  //缓存文件夹名称
        //外部存储
        builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context));
        builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context, diskCacheSize));
        builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,diskCacheFileName, diskCacheSize));
        //内部存储
        builder.setDiskCache(new InternalCacheDiskCacheFactory(context));
        builder.setDiskCache(new InternalCacheDiskCacheFactory(context, diskCacheSize));
        builder.setDiskCache(new InternalCacheDiskCacheFactory(context,diskCacheFileName, diskCacheSize));
    }
}

应用程序还可以自行选择 DiskCache 接口的实现,并提供自己的 DiskCache.Factory 来创建缓存。Glide 使用一个工厂接口来在后台线程中打开磁盘缓存,这样方便缓存做诸如检查路径存在性等的IO操作而不用触发 严格模式 。

@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        builder.setDiskCache(new DiskCache.Factory() {
            @Nullable
            @Override
            public DiskCache build() {
                return new DiskCache() {
                    //省略方法实现
                };
            }
        });
    }
}

三、默认请求选项

通常情况下请求选项 由每个请求来单独指定,但是我们也可以通过 AppGlideModule 配置默认请求选项以作用于你应用中启动的每个加载:

@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        RequestOptions defaultOptions = new RequestOptions()
                .centerCrop()
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .skipMemoryCache(true);
        builder.setDefaultRequestOptions(defaultOptions);
    }
}

尽管你已经在AppGlideModule中配置了默认请求选项,任何单独请求里应用的选项还是会覆盖 GlideBuilder 里设置的冲突选项

你可能感兴趣的:(Android 图片加载(三)图片加载框架Glide 进阶篇)