跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Kotlin大前端java

Glide 加载 WebP 动画的常见问题及解决方案

使用 Glide 加载 WebP 动画时因共享 AnimatedImageDrawable 实例导致的播放进度同步、停止联动及重播闪烁问题。通过对比清除全部内存缓存和使用第三方解码库的方案,最终提出自定义 GlideModule 拦截缓存 Key 的策略,实现精准清理特定资源缓存,避免影响其他图片加载并解决状态复用问题。

灭霸发布于 2026/4/5更新于 2026/9/877 浏览
Glide 加载 WebP 动画的常见问题及解决方案

问题现象

使用 Glide 图片加载框架加载 WebP 时,默认会将资源加载一份并缓存,后续引用相同资源 ID 会返回同一个缓存对象。这本身是常见的优化手段,但在 Android 原生 AnimatedImageDrawable 场景下会出现问题。因为 Glide 内部默认加载的 WebP 图片使用的是 AnimatedImageDrawable 类。

  1. 多个 View 通过 Glide 显示同一个 WebP 资源,会导致播放进度强制一致。

先启动上面的 ImageView 播放,再将图片设置到下面的 ImageView。结果后启动的开始时机被强制绑定到了和正在播放的一起。

  1. 任何一个调用了停止,其他的也会跟随停止。

调用 Glide.with(this).clear(imageView2) 将第二个 ImageView 播放停止并清除不显示,导致第一个 ImageView 也跟着停止了播放。

  1. 同一个 View 需要隐藏后再重头播放会导致开始时候闪现一下停止时的那一帧的问题。

将同一个资源在上面 ImageView 停止后设置给了第二个 ImageView,仔细观察会发现,第二个 ImageView 播放前会闪烁一下停止时候的那一帧。即使开始和停止在同一个 ImageView 发生也同样有这个问题。

解决问题的思路

开发中主要遇到的是重新播放闪现停止帧的问题。第一时间想到的是想办法避免这个问题:

在停止的时候释放掉这个播放进度的状态,回归为 0 帧位置。如果停止的时候做不到,那么在开始的时候想办法回归为 0 帧位置。

然而,查遍了系统 API,也没有见到 AnimatedImageDrawable 为用户开放对应的操作方法。

另外,即使使用 ASM 字节码修改框架等手段做到了控制帧,但这只能解决重复播放闪烁停止帧(即现象 3)。现象 1、现象 2 依然无法解决。因为它们是用的同一个 AnimatedImageDrawable 对象,所以会导致多个 View 视图播放进度是一个。

那么归根结底要解决的问题是要让每个 View 的播放状态不强关联的,并且重新播放不闪烁。想要做到这个只能每次使用都创建新的实例,因为旧的实例不提供清理状态的方法。而且不同的 View 也不可以复用同一个实例否则导致状态显示不能差异化。

方向明了后,我们只要按这个思路实现就能解决这个问题。下面有 2 个方案:

  1. 抛弃 Glide 框架,自己管理 WebP 资源的加载和执行控制。
  2. 还想用 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。并且简单查了下资料,AnimatedImageDrawable 的性能优势比 WebpDrawable 是巨大的。再就是如果是仅仅是单 View 显示的 WebP 文件加载出现的闪烁问题,那么在不是提前调用 stop 导致停在了中间的场景,完全没必要清除内存。可以在启动的时候复用重新复用也不会有闪烁问题。用第 3 种解决方案更灵活,不像当前方案在一开始就硬编码了强制不用缓存。

基于以上种种,我实际项目中没用这个方法。

Glide.with(this).load(resourceId).skipMemoryCache(true).into(this)

解决方法 3 最终解决

我们要想办法精准的在 Glide 的缓存中找到这个资源的缓存,只清除掉这一条,这样影响可以做到最小化。

但是!又要说但是了!Glide 的 API 并没有单独清理某一个资源的方法。那怎么弄。自定义 Module 吧!

Glide 为开发者开放了自定义 Module 的模块的功能,可以自己管理缓存的存储和释放,我这里遵循最小改动原则,存储结构还使用内置的规则。我们只在存储的时候过滤是否是 WebP 资源,是的话做存到列表中。做为以后清除他的时候做准备。

可能看到以下代码会有几处疑问,我提前解答一下吧。

问: 为什么要存储下来这个 key 列表? 答: 为了清除的时候能用对应的 key 去清除掉,否则你根本没办法根据你手里的 Resource id 名称来清除对应缓存。

问: 为什么要用 resource.toString().contains("AnimatedImageDrawableResource") 这种方式来判断是否是 WebP 资源? 答: 只能这么判断,系统没开放更多的 API。

问: 为什么删除类检索 key 的时候要倒着遍历? 答: 因为要边遍历,边删除元素,正着遍历不是漏掉就是数组越界异常!

自定义 GlideModule 类:

@GlideModule
class GlideCacheModule : AppGlideModule() {
    companion object {
        lateinit var glideMemoryCache: MemoryCache
        private val glideMemoryCacheKeyList: MutableList<Key> = mutableListOf()
    }

    override fun applyOptions(context: Context, builder: GlideBuilder) {
        super.applyOptions(context, builder)
        // 步骤 1:按 Glide 默认规则计算内存缓存大小(和 Glide 原生逻辑一致)
        val calculator = MemorySizeCalculator.Builder(context).build()
        val defaultMemoryCacheSize = calculator.memoryCacheSize
        // 步骤 2:创建 MemoryCache 实例(LruResourceCache 是 Glide 默认的内存缓存实现)
        val memoryCache = LruResourceCache(defaultMemoryCacheSize.toLong())
        // 步骤 3:保存实例到全局变量(供后续移除缓存使用)
        glideMemoryCache = object : MemoryCache {
            override fun getCurrentSize(): Long = memoryCache.currentSize
            override fun getMaxSize(): Long = memoryCache.maxSize
            override fun setSizeMultiplier(multiplier: Float) = memoryCache.setSizeMultiplier(multiplier)
            override fun remove(key: Key): Resource<*>? {
                glideMemoryCacheKeyList.remove(key)
                return memoryCache.remove(key)
            }
            override fun put(key: Key, resource: Resource<*>?): Resource<*>? {
                if (resource.toString().contains("AnimatedImageDrawableResource")) {
                    // 只缓存 AnimatedImageDrawableResource
                    glideMemoryCacheKeyList.add(key)
                }
                return memoryCache.put(key, resource)
            }
            override fun setResourceRemovedListener(listener: MemoryCache.ResourceRemovedListener) = memoryCache.setResourceRemovedListener(listener)
            override fun clearMemory() = memoryCache.clearMemory()
            override fun trimMemory(level: Int) = memoryCache.trimMemory(level)
        }
        // 步骤 4:将自定义的 MemoryCache 设置给 Glide
        builder.setMemoryCache(glideMemoryCache)
        // 可选:自定义内存缓存大小(比如设置为 20MB)
        // val customCacheSize = 20 * 1024 * 1024L
        // val customMemoryCache = LruResourceCache(customCacheSize)
        // glideMemoryCache = customMemoryCache
        // builder.setMemoryCache(customMemoryCache)
    }

    // 禁用 Manifest 解析,避免冲突(必写)
    override fun isManifestParsingEnabled(): Boolean = false
}

自定义删除工具类:

object GlideMemoryCacheRemover {
    /**
     * 移除指定 drawable 的内存缓存
     */
    fun removeDrawableMemoryCache(resId: Int) {
        // 2. 使用全局持有的 MemoryCache
        for (i in GlideCacheModule.glideMemoryCacheKeyList.size - 1 downTo 0) {
            val key = GlideCacheModule.glideMemoryCacheKeyList[i]
            if (key.toString().contains(resId.toString())) {
                GlideCacheModule.glideMemoryCache.remove(key)
            }
        }
    }
}

调用删除:

mResourcesId?.let { GlideMemoryCacheRemover.removeDrawableMemoryCache(it) }

目录

  1. 问题现象
  2. 解决问题的思路
  3. 解决方法 1 快速简洁但不推荐
  4. 解决方法 2 引入其他解码库来代替原生 AnimatedImageDrawable
  5. 解决方法 3 最终解决

更多推荐文章

查看全部
  • Java 中 new 创建对象的完整流程解析
  • OpenAI 与 LangChain 集成实战指南
  • Flutter for OpenHarmony 集成 dart_openai 实现 AIGC 功能
  • 昇腾 NPU 部署 Llama-2-7B 大模型实战与性能测试
  • 基于 OpenCV 和 Python 的车道线检测系统(带 UI 界面)
  • 前端大数据导出优化:解决 Chrome 内存崩溃的实战方案
  • ESP32 + MimiClaw BLDC 无刷电机驱动方案与实战案例
  • MiniMax 海螺 AI 视频:图文生成高质量视频实战指南
  • 飞算 JavaAI 2.0.0 评测:自然语言编程实战与效率分析
  • 自然语言处理在金融领域的应用与实战
  • 华为 OD 机试双机位 C 卷 - 数组连续和
  • Wildcard AI 服务评测:低价背后的模型真相
  • 中文类 LLaMA 大语言模型资源整理与对比
  • OrangePlayer:功能完整的 Android 视频播放器开源库
  • 资深工程师的 Web 前端开发全维准备指南
  • AI 绘画在商业设计中的应用与版权解析
  • C 语言指针与数组的深层关联及实战应用
  • MCP 插件配置指南:以 browser-tools-mcp 为例
  • Cursor AI 使用与 Git 版本控制指南
  • 使用 Web Scraper 插件抓取知乎评论数据实战

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online