问题现象
使用 Glide 图片加载框架加载 WebP 时,默认会将资源加载一份并缓存,后续引用相同资源 ID 会返回同一个缓存对象。这本身是常见的优化手段,但在 Android 原生 AnimatedImageDrawable 场景下会出现问题。因为 Glide 内部默认加载的 WebP 图片使用的是 AnimatedImageDrawable 类。
- 多个 View 通过 Glide 显示同一个 WebP 资源,会导致播放进度强制一致。
先启动上面的 ImageView 播放,再将图片设置到下面的 ImageView。结果后启动的开始时机被强制绑定到了和正在播放的一起。
- 任何一个调用了停止,其他的也会跟随停止。
调用 Glide.with(this).clear(imageView2) 将第二个 ImageView 播放停止并清除不显示,导致第一个 ImageView 也跟着停止了播放。
- 同一个 View 需要隐藏后再重头播放会导致开始时候闪现一下停止时的那一帧的问题。
将同一个资源在上面 ImageView 停止后设置给了第二个 ImageView,仔细观察会发现,第二个 ImageView 播放前会闪烁一下停止时候的那一帧。即使开始和停止在同一个 ImageView 发生也同样有这个问题。
解决问题的思路
开发中主要遇到的是重新播放闪现停止帧的问题。第一时间想到的是想办法避免这个问题:
在停止的时候释放掉这个播放进度的状态,回归为 0 帧位置。如果停止的时候做不到,那么在开始的时候想办法回归为 0 帧位置。
然而,查遍了系统 API,也没有见到 AnimatedImageDrawable 为用户开放对应的操作方法。
另外,即使使用 ASM 字节码修改框架等手段做到了控制帧,但这只能解决重复播放闪烁停止帧(即现象 3)。现象 1、现象 2 依然无法解决。因为它们是用的同一个 AnimatedImageDrawable 对象,所以会导致多个 View 视图播放进度是一个。
那么归根结底要解决的问题是要让每个 View 的播放状态不强关联的,并且重新播放不闪烁。想要做到这个只能每次使用都创建新的实例,因为旧的实例不提供清理状态的方法。而且不同的 View 也不可以复用同一个实例否则导致状态显示不能差异化。
方向明了后,我们只要按这个思路实现就能解决这个问题。下面有 2 个方案:
- 抛弃 Glide 框架,自己管理 WebP 资源的加载和执行控制。
- 还想用 Glide 框架,那么我们要想办法把 Glide 的内存缓存清掉,让 Glide 每次都重新创建。
解决方法 1 快速简洁但不推荐
每次设置 WebP 显示前直接调用 Glide.get(this.context).clearMemory() 清除所有内存缓存,这种方法简单快捷,缺点就是会误伤其他非 WebP 资源导致一起清空。
不过你也不用那么担心,他的这个整体也没那么不堪和呆板。Glide 内部的缓存会分为 active 区和普通区,active 就是当前资源绑定了 view 正在显示中的,调用 Glide.get(this.context).clearMemory() 的时候无论如何也不会清掉 active 这部分的缓存的。
如果项目需要应急,没太多时间修复的话,可以临时先用这个方法顶一顶。
解决方法 2 引入其他解码库来代替原生 AnimatedImageDrawable
引入库 com.github.zjupure:webpdecoder:2.6.4.16.0 并开启 Glide 的 skipMemoryCache 选项。
因为该库使用的自己的 api 进行存储的 WebP 内存资源。所以不存在这个问题。
那么读者可能会问,直接不引入这个 Module 库,直接使用
skipMemoryCache不行么?那这还真不行,skipMemoryCache会和AnimatedImageDrawable产生冲突,导致必然崩溃。
这个方法我认为存在的缺点就是必须在项目中指定的位置单独调用
skipMemoryCache,不然这个skipMemoryCache会干扰到其他的图片缓存。如果你是封装的 Glide 的话,改动起来比较麻烦。并且这个库也会修改解析的 Drawable 类型为 com.bumptech.glide.integration.webp.decoder.WebpDrawable。并且简单查了下资料,。再就是如果是仅仅是单 View 显示的 WebP 文件加载出现的闪烁问题,那么在不是提前调用 stop 导致停在了中间的场景,完全没必要清除内存。可以在启动的时候复用重新复用也不会有闪烁问题。,不像。


