前言
系列文章专栏: 玩转Glide4
概述
加载相同的网络资源的时候,为了减少流量开销,优化加载速度,我们一般都会选择使用缓存。
Glide作为最优秀的图片加载库,广受大家认可,必然也有着十分优秀的缓存机制。
Glide缓存基于LRU原理实现了两种缓存:
- 内存缓存MemoryCache
- 硬盘缓存DiskCache
Glide是默认同时开启这两种缓存的。
内存缓存MemoryCache
基于LRU的缓存驱逐算法实现。
默认开启,也可以调用skipMemoryCache(boolean skip)
开启或者关闭。
内存缓存的内容并不会保存到手机存储里,而是临时存在一个固定大小的HashMap中。
因此重启App上次的缓存就会失效,需要重新加载。
硬盘缓存DiskCache
4.x硬盘缓存有如下四种缓存策略:
模式 | 说明 | 备注 |
---|---|---|
NONE | 不缓存任何内容 | |
DATA | 只缓存原始图片 | 相当于3.x的SOURCE |
RESOURCE | 只缓存转换过后的图片 | 相当于3.x的RESULT |
ALL | 既缓存原始图片,也缓存转换过后的图片 | |
AUTOMATIC | 默认选项,Glide会根据图片资源智能地选择使用哪一种缓存策略 | 4.x新增 |
注意:4.x缓存策略与3.x略不同,3.x升级到4.x此处需要注意,尤其是要注意3.x默认的缓存策略是RESULT
调用diskCacheStrategy(@NonNull DiskCacheStrategy strategy)
即可配置缓存策略。
只要不是NONE策略,使用Glide加载图片,就会默认将图片缓存到硬盘上。
默认的存储路径是内置存储的沙盒环境的cache/image_manager_disk_cache
目录。
数据源DataSource
如果你看过DiskCacheStrategy
的代码就知道,Glide判断是否缓存的依据就算判断DataSource
。
DataSource
是一个枚举类:
数据源 | 说明 |
---|---|
LOCAL | 本地文件 |
REMOTE | 远程链接 |
DATA_DISK_CACHE | 原始硬盘缓存 |
RESOURCE_DISK_CACHE | 转换后的硬盘缓存 |
MEMORY_CACHE | 内存缓存 |
通过listener(@Nullable RequestListener<TranscodeType> requestListener)
方法,我们就可以在onResourceReady
回调中判断加载图片完成时,数据源来自哪。
val optionInto = RequestOptions()
.transform(CircleCrop(), BlurTransformation())
.skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
Glide.with(this)
.load(ConstUrl.ImgOne)
.apply(optionInto)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
Log.i(TAG, "onLoadFailed: ")
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
Log.i(TAG, "onResourceReady: $dataSource")
return false
}
})
.into(iv)
如上代码。
测试远程链接
- 第一次加载一个新图片,DataSource==REMOTE
- 不重启App,再次加载改图片,DataSource==MEMORY_CACHE
- 重启App,加载该图片,DATA_DISK_CACHE==REMOTE
- 不重启App,再次加载改图片,DataSource==MEMORY_CACHE
- 清除App缓存,加载该图片DataSource==REMOTE
测试资源drawable
我们将代码修改为加载drawable: load(R.drawable.image_long)
- 第一次加载一个新图片,DataSource==LOCAL
- 再后面重新加载该图片,都是DataSource==RESOURCE_DISK_CACHE
缓存签名Key
资源文件
上面测试drawable时,我们知道Glide甚至会缓存资源文件。
我们给上面的R.drawable.image_long
换一张图片,然后重启App,你会惊奇的发现仍旧显示的是上一个图片的内容。
这个时候你肯定一脸的怀疑人生,有两个大大的疑问:
- 加载资源文件我是不是没有关掉缓存?
- Glide加载的资源适配了夜间模式岂不是无效?
毕竟现在日常开发中,适配夜间模式和设计图更换是家常便饭,夜间模式要求同名不说,设计图更换一般也是直接替换而不会去改资源名称。
如果这个时候资源文件加载没变,不是得背大锅?但是不对啊,为什么我开发过程没有发现这个问题呢?
莫慌,让我们看看源码压压惊~
public RequestBuilder<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer resourceId) {
return loadGeneric(resourceId).apply(signatureOf(AndroidResourceSignature.obtain(context)));
}
我们再看一下这段代码的注解~
默认情况下,此方法将版本代码和基于夜间模式的签名添加到用于在 Glide 中缓存此资源的缓存键。此签名足以保证最终用户将看到您的 Drawable 的最新版本,但在开发过程中,如果您在每次安装之前不增加您的版本代码,并且您在不更改 Drawable 名称的情况下用不同的数据替换 Drawable,您可能会看到不一致的缓存数据。要解决此问题,请考虑在开发期间通过RequestOptions.diskCacheStrategy(DiskCacheStrategy)使用DiskCacheStrategy.NONE ,并为发布版本重新启用默认DiskCacheStrategy.RESOURCE
哈哈,简单解释:Glide已经非常人性化的做了夜间模式切换,而且资源的缓存也是根据版本号来判断的。
打开AndroidResourceSignature
类的源码,版本号&夜间模式判断代码如下:
@NonNull
public static Key obtain(@NonNull Context context) {
Key signature = ApplicationVersionSignature.obtain(context);
int nightMode =
context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
return new AndroidResourceSignature(nightMode, signature);
}
因此这里也提醒我们如果要使用Glide加载资源文件,资源文件更换后,必须修改版本号;如果确实特殊原因无法修改版本号,就要使用DiskCacheStrategy.NONE
关掉缓存。
远程链接
所有同学都知道,Glide判断是否需要缓存的依据就是链接url字符串,如果一个url已经缓存过,就直接使用缓存而不会重新加载。
日常开发中也确实如此,理论且合理的来说,每个不同的图片都应该对应唯一的远程链接,不应有重复链接的现象的出现。
但是理想很美好,现实却不是如此。
以我们项目为例,由于旧系统设计的不合理,用户头像是用一个固定链接+用户id拼接而成。
诚然这样获取用户头像链接很方便,却导致一个头疼的问题:用户修改头像后如何刷新缓存?
说到刷新缓存这就要用到方法:signature(@NonNull Key signature)
设置一些额外的数据混合到内存和磁盘缓存键中,允许调用者更好地控制缓存数据何时失效。
4.x移除了StringSignature
新增了ObjectKey
,ObjectKey允许我们使用任何类型的变量来控制刷新缓存。
如调用这段代码.signature(ObjectKey(System.currentTimeMillis()))
,表明我们使用使用时间戳去刷新。
这时即使我们开启了全部缓存,每次加载都是DataSource==REMOTE。
其实使用微信时我们也会发现,微信对方的头像即使更换了也不会实时更新,只有我们点击他的头像,到个人页时才会刷新。
因此针对这种情况我们可以:
- Glide第一次加载用户头像时,用户id+时间戳生成一个key,调用
signature()
- 将上面生成的key保存到SharedPreferences等本地。
- 访问特定页面,如用户空间页时就刷新key的值。
- 用户自己的头像,则可以在修改头像成功后刷新key。
缓存配置
Glide4.x最新版本4.13.1GlideModule&AppliesOptions
已经被弃用,亲测在此进行配置也无法生效,因此升级到最新版本的同学要注意啦。
取而代之的是Glide同其他库一样新增了初始化方法。
class App : Application() {
override fun onCreate() {
super.onCreate()
val builder: GlideBuilder = GlideBuilder()
//设置内存缓存大小为20mb
val memoryCacheSize = 1024 * 1024 * 20 // 20mb
//设置内存缓存大小
builder.setMemoryCache(LruResourceCache(memoryCacheSize.toLong()))
//设置硬盘缓存大小为200mb
val diskCacheSize = 1024 * 1024 * 200 // 200mb
//根据SD卡是否可用选择是在内部缓存还是SD卡缓存
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
builder.setDiskCache(ExternalPreferredCacheDiskCacheFactory(this, "Glide_images", diskCacheSize.toLong()))
} else {
builder.setDiskCache(InternalCacheDiskCacheFactory(this, "Glide_images", diskCacheSize.toLong()))
}
Glide.init(this, builder)
}
}
清除缓存
清除内存缓存
Glide会在onLowMemory时自动调用。
//必须在主线程上调用
Glide.get(this).clearMemory()
清除硬盘缓存
Thread {
//此方法应始终在后台线程上调用,因为它是阻塞调用
Glide.get(this).clearDiskCache()
}.start()