Glide播放webp动画的一些坑

Glide播放webp动画的一些坑

问题现象

使用Glide图片加载框架加载webp的时候默认会将一个资源加载一份然后缓存起来,之后引用相同资源id会始终返回这同一个缓存。这本身是一个很常见的优化手段,但是遇到Android原生的AnimatedImageDrawable就会有问题。因为Glide内部如果不做自定义Module的话,默认加载的webp图片就是使用的AnimatedImageDrawable类。

  1. 如果有多个view通过Glide显示同一个webp资源,会导致播放进度强制一致。
其实我是先开始start播放上面的ImageView,然后再将图片设置到了下面的ImageView。结果后start的开始时机被强制绑定到了和正在播放的一起。
在这里插入图片描述
  1. 任何一个调用了停止其他的也会跟随停止。
这里我调用Glide.with(this).clear(imageView2);将第二个ImageView播放停止并清楚不显示,导致第一个ImageView也跟着停止了播放。
在这里插入图片描述
  1. 同一个view需要隐藏后再重头播放会导致开始时候闪现一下停止时的那一帧的问题。
这里我将同一个资源在上面ImageView停止后设置给了第二个ImageView,仔细观察会发现,第二个ImageView播放前会闪烁一下停止时候的那一帧。这里只是为了方便演示,即使开始和停止在同一个ImageView发生也同样有这个问题。
在这里插入图片描述


如果觉得git图看不清的可以看视频,视频的帧率更高一下,对于第3个现象看的更明显

my_screen_record

解决问题的思路

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

在停止的时候释放掉这个播放进度的状态,回归为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类:

@com.bumptech.glide.annotation.GlideModule class GlideCacheModule :AppGlideModule(){// 全局持有MemoryCache实例(核心:替代之前的初始化器方式)companionobject{lateinitvar glideMemoryCache: MemoryCache privatesetval glideMemoryCacheKeyList: MutableList<Key>=mutableListOf()}overridefunapplyOptions(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{overridefungetCurrentSize(): Long {return memoryCache.currentSize }overridefungetMaxSize(): Long {return memoryCache.maxSize }overridefunsetSizeMultiplier(multiplier: Float){ memoryCache.setSizeMultiplier(multiplier)}overridefunremove(key: Key): Resource<*>?{ glideMemoryCacheKeyList.remove(key)return memoryCache.remove(key)}overridefunput( key: Key, resource: Resource<*>?): Resource<*>?{if(resource.toString().contains("AnimatedImageDrawableResource")){// 只缓存 AnimatedImageDrawableResource glideMemoryCacheKeyList.add(key)}return memoryCache.put(key, resource)}overridefunsetResourceRemovedListener(listener: MemoryCache.ResourceRemovedListener){ memoryCache.setResourceRemovedListener(listener)}overridefunclearMemory(){ memoryCache.clearMemory()}overridefuntrimMemory(level: Int){ memoryCache.trimMemory(level)}}// 步骤4:将自定义的MemoryCache设置给Glide(替代setMemoryCacheInitializer) builder.setMemoryCache(glideMemoryCache)// 可选:自定义内存缓存大小(比如设置为20MB)// val customCacheSize = 20 * 1024 * 1024L // 20MB// val customMemoryCache = LruResourceCache(customCacheSize)// glideMemoryCache = customMemoryCache// builder.setMemoryCache(customMemoryCache)}// 禁用Manifest解析,避免冲突(必写)overridefunisManifestParsingEnabled(): Boolean {returnfalse}}

自定义删除工具类:

object GlideMemoryCacheRemover {/** * 移除指定drawable的内存缓存 */funremoveDrawableMemoryCache(resId: Int){// 2. 使用全局持有的MemoryCachefor(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)}

Read more

腾讯扔出“王炸”|微信变身AI超级入口:Qclaw免费内测,三步上手攻略

腾讯扔出“王炸”|微信变身AI超级入口:Qclaw免费内测,三步上手攻略

文章目录 * 使用教程 过去,大家总觉得AI工具有门槛——要配置环境、学习指令、切换应用,繁琐得像换一台新电脑。 但现在,Qclaw把这一切彻底打破。 从下载到使用,只需三步,全程不超过3分钟。 没有复杂的设置,没有技术门槛,真正做到了“傻瓜式操作,专业级体验”。 第一步:下载安装 前往 Qclaw 官网(https://claw.guanjia.qq.com/),根据你的系统(Mac / Windows)下载安装包,一键安装,无需任何开发环境配置,耗时不到2分钟。 第二步:扫码绑定 打开电脑端 Qclaw,用微信扫描界面上的二维码,30秒内即可完成绑定。 从此,你的微信就成了Qclaw的“远程遥控器”。 第三步:发送指令 在微信里直接对Qclaw说你想做的事——无论是处理文档、操作电脑,还是执行某个具体任务,

By Ne0inhk

ANSYS Fluent 2026 R1新功能实测:从汽车风阻优化看AI加速流体仿真

ANSYS Fluent 2026 R1新功能实测:AI如何重塑汽车风阻优化流程 当电动汽车的续航里程成为消费者最关注的指标之一时,风阻系数每降低0.01都意味着实际道路行驶中可观的续航提升。传统CFD仿真虽然能提供准确的气动特性预测,但工程师们长期受限于网格划分的繁琐和计算资源的消耗。ANSYS Fluent 2026 R1的发布,通过深度整合AI技术,正在彻底改变这一局面。 1. AI赋能的网格生成革命 在传统CFD工作流程中,网格划分往往占据整个项目周期的60%以上时间。Fluent 2026 R1引入的AI-Mesh技术,通过机器学习模型自动识别几何特征并预测最优网格密度分布,将这一过程缩短至原来的1/5。 以某电动汽车外流场分析为例,我们对同一车型分别采用传统方法和AI-Mesh进行对比测试: 参数传统方法AI-Mesh差异网格生成时间4.2小时47分钟-82%网格数量1200万980万-18%y+平均值1.20.9-25%近壁层网格正交质量0.850.92+8% 关键改进细节: * 几何特征自动识别:AI模型可准确识别车门缝隙、后视镜边缘等关键区域

By Ne0inhk

基于LangGraph实现模块化Skills型AI Agent

基于LangGraph+DeepSeek+Serper 实现模块化Skills型AI Agent 在AI Agent的落地实践中,模块化Skills设计是提升Agent可扩展性、可维护性的核心方案——将搜索、计算、文件处理等能力封装为独立Skills,Agent可根据需求自主调用,无需修改核心流程。本文将基于LangGraph、DeepSeek大模型和Serper搜索工具,手把手带你实现一个具备工具调用能力的Skills型AI Agent,同时解决开发中常见的MRO冲突、Pydantic验证等问题,代码可直接复制运行。 一、前言:为什么选择Skills型Agent? 传统AI Agent多采用「硬编码工具调用」的方式,新增能力需修改核心逻辑,耦合度高且难以维护。而Skills型Agent将能力拆分为独立的Skill模块,每个Skill遵循统一接口,具备以下优势: 1. 模块化解耦:新增/修改Skill无需改动Agent核心流程,即插即用; 2. 智能决策:大模型自主判断是否调用Skill、调用哪个Skill,无需人工干预; 3. 可扩展性强:支持搜索、计算、代码解释、数

By Ne0inhk
OpenClaw视觉操作实战:不写接口,让AI直接点按钮、操作软件

OpenClaw视觉操作实战:不写接口,让AI直接点按钮、操作软件

文章目录 * 前言 * 一、OpenClaw是啥?你的数字长工 * 二、视觉操作的核心:Snapshot快照系统 * 1. 告别元素定位地狱 * 2. 自适应界面变化 * 3. 跨应用操作 * 三、实战:手把手教你让AI自动填表 * 步骤1:安装与环境准备 * 步骤2:启动视觉模式 * 步骤3:编写自动化脚本 * 步骤4:进阶:自动下载报表 * 四、不止浏览器:桌面软件也能点 * 五、定时任务:让AI自己起床干活 * 六、数据安全:你的隐私留在本地 * 七、避坑指南:新手常踩的雷 * 1. 动态加载的坑 * 2. 弹窗处理 * 3. API额度控制 * 4. 元素编号会变 * 八、总结:从“码农”

By Ne0inhk