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

人工智能:深度学习中的卷积神经网络(CNN)实战应用

人工智能:深度学习中的卷积神经网络(CNN)实战应用

人工智能:深度学习中的卷积神经网络(CNN)实战应用 1.1 本章学习目标与重点 💡 学习目标:掌握卷积神经网络的核心原理、经典网络架构,以及在图像分类任务中的实战开发流程。 💡 学习重点:理解卷积层、池化层的工作机制,学会使用 TensorFlow 搭建 CNN 模型并完成训练与评估。 1.2 卷积神经网络核心原理 1.2.1 卷积层:提取图像局部特征 💡 卷积层是 CNN 的核心组件,其作用是通过卷积核对输入图像进行局部特征提取。 卷积核本质是一个小型的权重矩阵。它会按照设定的步长在图像上滑动。每滑动一次,卷积核就会与对应区域的像素值做内积运算,输出一个特征值。 这个过程可以捕捉图像的边缘、纹理等基础特征。 ⚠️ 注意:卷积核的数量决定了输出特征图的通道数,数量越多,提取的特征维度越丰富。 ① 定义一个 3×3 大小的卷积核,步长设为 1,填充方式为 SAME

边缘AI:解锁终端设备的智能潜能

边缘AI:解锁终端设备的智能潜能

边缘AI:解锁终端设备的智能潜能 摘要 边缘AI(Edge AI)作为人工智能领域的重要演进方向,正以前所未有的速度改变着我们与技术交互的方式。本文深入探讨边缘AI的核心概念、技术架构、优势挑战及实际应用。我们将系统解析边缘AI与传统云端AI的本质区别,详解其关键技术如模型轻量化、硬件加速和联邦学习,并通过多个实践代码示例展示如何在资源受限的终端设备上部署智能模型。文章还将对比不同边缘AI框架,分析典型应用场景,并展望未来发展趋势。读者将全面理解边缘AI的技术原理、实现方法及其如何真正"解锁终端设备的智能潜能",为实际项目部署提供清晰的技术路线图。🧠 引言:从云端到边缘的范式转变 传统人工智能系统大多采用"云中心"架构,将海量数据上传至远程服务器进行处理分析,再将结果返回终端设备。这种模式在深度学习兴起初期表现卓越,但随着物联网设备爆炸式增长、数据隐私要求日益严格以及对实时性需求的不断提升,其局限性逐渐凸显:网络延迟、带宽成本、数据安全隐患和单点故障等问题日益突出。 边缘AI应运而生,它代表着一种根本性的范式转变——将人工智能模型的推理(甚至训练)能力直接部署到数据产生

IDEA 插件 Trae AI 全攻略

IDEA 插件 Trae AI 全攻略

在 Java 开发的日常中,你是否经常遇到这些场景: * 面对重复的 CRUD 代码,机械敲击键盘却内心抗拒? * 接手 legacy 系统,看着几百行的复杂逻辑无从下手? * 调试时卡在某个异常,翻遍文档和 Stack Overflow 却找不到答案? * 写单元测试时,明明功能简单却要耗费大量时间设计测试用例? 这些问题的核心,在于重复性工作占用了太多创造性时间。而随着 AI 技术的发展,AI 辅助开发工具已成为突破效率瓶颈的关键。在众多工具中,Trae AI作为 IDEA 的一款插件,凭借对 Java 生态的深度适配、与 IDE 的无缝集成以及强大的代码理解能力,逐渐成为开发者的 “编码搭子”。 本文将从基础到进阶,全面讲解 Trae AI 的功能、用法、实战技巧和最佳实践,帮你彻底释放 AI 辅助开发的潜力,让编码效率提升

保姆级教程:Windows本地部署Ollama+OpenClaw,打造你的AI赚钱系统(APP开发/量化/小说/剪辑)

摘要:想用AI搞钱但卡在技术门槛?本文手把手教你用一台Windows电脑,零成本本地部署Ollama大模型+OpenClaw智能中枢,赋予AI开发APP、量化分析、编写小说、剪辑辅助等“赚钱技能”。全程无需编程基础,跟着鼠标点、照着命令敲,即可拥有24小时待命的AI员工。 一、写在前面 很多朋友对AI变现跃跃欲试,却常被这些问题劝退: * 云端部署太贵,API调用怕浪费钱 * 技术文档看不懂,不知道从哪下手 * 数据隐私担忧,不敢把敏感资料上传 其实,你手头那台Windows电脑完全能胜任!本文将带你搭建一套完全本地化、免费、可扩展的AI生产力系统,让AI帮你写代码、分析表格、生成文案、处理视频,真正把AI变成你的“赚钱工具”。 系统架构: * 本地大脑:Ollama + DeepSeek模型,负责理解任务、生成内容 * 智能中枢:OpenClaw(原名OpenClaude),负责调用各类工具(Skill) * 赚钱技能:通过安装Skill包,让AI具备特定领域的实操能力 适用人群: