问题现象
在使用 Glide 加载 WebP 图片时,默认会将资源加载一份并缓存。后续引用相同资源 ID 时,会直接返回同一个缓存对象。这种机制本身是为了优化性能,但在 Android 原生的 AnimatedImageDrawable 场景下会带来麻烦。
如果不自定义 Module,Glide 默认加载的 WebP 就是使用 AnimatedImageDrawable 类。这会导致以下三个典型问题:
-
多视图播放进度强制同步 如果有多个 ImageView 通过 Glide 显示同一个 WebP 资源,它们的播放进度会被绑定在一起。比如先启动上面的 ImageView,再设置下面的 ImageView,后者的开始时机也会被强制同步到正在播放的状态。
-
停止操作相互影响 任何一个调用了停止或清除操作的 View,其他关联 View 也会跟随停止。例如调用
clear()停止第二个 ImageView 的播放,第一个 ImageView 也会跟着停掉。 -
重播出现闪烁 同一个 View 需要隐藏后再重头播放时,会出现闪现停止帧的问题。即使是在同一个 ImageView 上发生停止和重新开始,仔细观察也能发现这一帧的抖动。
解决思路
核心问题在于每个 View 的播放状态被强关联了,且旧实例无法提供清理状态的方法。想要彻底解决,必须让每个 View 拥有独立的实例,或者在复用前精准重置状态。
经过排查,系统 API 没有开放控制帧的操作方法。ASM 字节码修改虽然能控制帧,但只能解决重播闪烁,无法解决多视图进度同步问题。归根结底,要打破实例共享,要么抛弃 Glide 自己管理,要么在 Glide 内部做文章。
解决方案
方案一:全局清空内存缓存(不推荐)
每次设置 WebP 显示前,直接调用 Glide.get(context).clearMemory() 清除所有内存缓存。
这种方法简单快捷,但副作用明显。它会误伤其他非 WebP 资源。不过也不必过于担心,Glide 的缓存分为 active 区和普通区,active 区是当前绑定了 View 正在显示的,clearMemory() 无论如何也不会清掉这部分。如果项目急需应急且没时间重构,可以临时顶一顶。
方案二:跳过缓存 + 第三方库(有局限)
引入 com.github.zjupure:webpdecoder 库并开启 Glide 的 skipMemoryCache 选项。该库使用自己的 API 存储 WebP 内存资源,不存在共享实例问题。
注意,不能只开 skipMemoryCache 而不引入库,否则会和 AnimatedImageDrawable 冲突导致崩溃。这个方案的缺点也很明显:必须在项目指定位置单独调用配置,否则干扰其他图片缓存;封装 Glide 时改动较大;且第三方库解析的 Drawable 类型性能不如原生 AnimatedImageDrawable。如果是单 View 显示且不涉及提前停止导致的中间帧问题,其实没必要一开始就硬编码强制不用缓存。
Glide.with(this).load(resourceId).skipMemoryCache(true).into(imageView)
方案三:自定义 Module 精准清理(推荐)
最佳实践是精准地在 Glide 缓存中找到 WebP 资源的缓存,只清除这一条,将影响降到最低。
Glide 提供了自定义 Module 的功能,允许我们管理缓存的存储和释放。遵循最小改动原则,我们保留内置规则,只在存储时过滤 WebP 资源,将其 Key 记录下来,以便后续精准移除。
这里有个关键点:为什么要存 Key 列表? 因为清除时需要用对应的 Key 去删除,光靠 Resource ID 名称无法定位缓存。另外,删除时要倒序遍历,避免边遍历边删除元素导致数组越界。
1. 自定义 GlideModule
我们需要拦截 applyOptions,创建自定义的 MemoryCache 包装器。当存入资源时,判断是否为 AnimatedImageDrawableResource,如果是则记录 Key。
@GlideModule
: () {
{
glideMemoryCache: MemoryCache
glideMemoryCacheKeyList = mutableListOf<Key>()
}
{
.applyOptions(context, builder)
calculator = MemorySizeCalculator.Builder(context).build()
defaultMemoryCacheSize = calculator.memoryCacheSize
memoryCache = LruResourceCache(defaultMemoryCacheSize.toLong())
glideMemoryCache = : MemoryCache {
: = memoryCache.currentSize
: = memoryCache.maxSize
= memoryCache.setSizeMultiplier(multiplier)
: Resource<*>? {
glideMemoryCacheKeyList.remove(key)
memoryCache.remove(key)
}
: Resource<*>? {
(resource?.toString()?.contains() == ) {
glideMemoryCacheKeyList.add(key)
}
memoryCache.put(key, resource)
}
{
memoryCache.setResourceRemovedListener(listener)
}
= memoryCache.clearMemory()
= memoryCache.trimMemory(level)
}
builder.setMemoryCache(glideMemoryCache)
}
: =
}


