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

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

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


一、变换

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

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

1. 默认变换

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

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 里设置的冲突选项

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容