# svgaplayer-android **Repository Path**: hljdrl/svgaplayer-android ## Basic Information - **Project Name**: svgaplayer-android - **Description**: android svga动画播放库,基于源动画库:修复已知问题、优化性能、扩展功能。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2024-08-21 - **Last Updated**: 2026-07-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: android-svga, svga ## README # ffsky-svgaplayer-android 基于原始仓库com.github.yyued:SVGAPlayer:3.6.1版本,继续bug修复和优化 1. 修复-带音频的svga动画播时,svga动画中音频audiokey相同,导致多组动画声音文件缓存命中第一个动画的音频. 2. SVGAVideoShapeEntity.kt--->checkAlphaValueRange: 修复-增加非空判断,部分动画到处后在android平台无法正常解析播放. ### 已知限制 | 已知限制 | 实现方式 | 优化方案 | |---|---|---| | Android `SoundPool` 单个音频超过约 5 秒可能播放不全(尤其 MP3 格式) | SVGA 内部使用 `SoundPool` 实现低延迟音频播放;`SoundPool` 将音频解码到内存,存在大小上限,导致较长音频被截断 | 制作 SVGA 时将单个音效控制在 5 秒以内,或通过 `PlayCallback` 接口使用 `MediaPlayer` 自行实现音频播放 | ### Install Via Gradle ``` //最新版本,已发布到maven中心仓库 api "com.gitee.hljdrl:svgaplayer-android:2.6.8.0" ``` #### 2.6.8.0 更新内容 **Features** - `SVGADrawable` 新增 `renderFrame(frameIndex)` 方法,支持离屏渲染指定帧 - 网络下载器 `SVGAFileDownloader` 拆分为独立类,支持自定义实现 - `SVGACache` 新增 `clearCacheByUrl(url)` 按 URL 清除单个动画缓存 - ZIP 缓存写入失败时自动清理不完整缓存目录,防止永久加载失败 - Demo 新增「动画帧预览」页面(逐帧缩略图、大图预览、倍速播放、内存监测波动图) - Demo 新增 `MemoryChartView` 实时内存监测控件,动画列表和播放页面均可查看 - Demo 新增 `OkHttpSVGAFileDownloader` OkHttp 异步下载实现 - Demo 网络动画页面增加按 URL 清除缓存按钮 **Docs** - 更新全部 Demo 截图,新增帧预览页面截图 --- ``` //上一版本 api "com.gitee.hljdrl:svgaplayer-android:2.6.7.0" ``` #### 2.6.7.0 更新内容 **Features** - `SVGACache.onCreate` 新增 `useExternal` 参数,支持外部扩展存储缓存目录 - 新增 `SVGACacheValidator` 工具类,基于内容 MD5 指纹校验 assets 缓存有效性 - 新增 `SVGACache.validateAssetCaches` 方法,后台自动比对并清理过期缓存 - 动画解析完成日志增加输入文件名和缓存目录路径 - Demo Assets 动画列表支持二级目录递归遍历 - Demo 增加「缓存检查」和「清理缓存」功能 --- ``` //历史版本 api "com.gitee.hljdrl:svgaplayer-android:2.6.6.0" ``` #### 2.6.6.0 更新内容 **Features** - 新增工具类 `SVGAVideoInfo`,提供动画时长、音频轨道数量、原始音频文件时长查询 - Demo Assets 动画列表增加动画参数、音频信息、文件大小显示 - Demo Assets 动画列表全屏播放增加背景颜色选择 **Docs** - 新增「已知限制」章节,说明 SoundPool 音频时长限制及优化方案 **Bug Fixes** - 修复动画列表格式化字符串参数缺失导致崩溃 - 添加 `noCompress 'svga'` 防止 APK 压缩 SVGA 文件 --- ``` //历史版本 api "com.gitee.hljdrl:svgaplayer-android:2.6.5.0" ``` #### 2.6.5.0 更新内容 **Features** - 新增公开属性 `SVGAVideoEntity.hasAudio`,方便外部判断动画是否包含音频 - Demo 新增「音频动画播放测试」示例页面 - Demo Assets 动画列表增加音频标识 🔊 显示 --- ``` //历史版本 api "com.gitee.hljdrl:svgaplayer-android:2.6.4.0" ``` #### 2.6.4.0 更新内容 **Bug Fixes — 严重修复 (P0)** - FILE 模式缓存写入未检查 inflate 是否成功,失败时仍写入缓存导致后续加载永久失败 - DEFAULT 模式缓存异步写入未对 inflatedBytes 做空判断,inflate 失败时工作线程 NPE 崩溃 - `totalTime == 0` 时跳过 SoundPool 加载,导致 protobuf 未设置 startTime/totalTime 的 SVGA 音频无法播放 - 音频格式检测从仅匹配单一 MP3 配置扩展为通用 MP3/AAC 帧同步检测,覆盖所有编码格式 **Bug Fixes — 内存泄漏 / UI 线程 (P1)** - 解析完成时 View 已被 GC 回收,未清理 videoItem 导致 Bitmap/SoundPool 等资源泄漏 - `SVGAVideoEntity.clear()` 中 `SoundPool.release()` 从 UI 线程同步执行改为后台线程,避免 5-100ms UI 阻塞 - `SVGASoundManager.release()` 中 `SoundPool.release()` 移到锁外执行,避免长耗时 native 调用阻塞 UI 线程 --- ``` //历史版本 api "com.gitee.hljdrl:svgaplayer-android:2.6.3.0" ``` #### 2.6.3.0 更新内容 **Build & Toolchain 升级** - AGP 7.4.2 → 8.5.2,Gradle 7.5 → 8.7,Kotlin 1.9.0 → 1.9.24 - compileSdk 28 → 34,targetSdk 33 → 34,minSdk 14 → 21 - 迁移废弃 API(`packagingOptions` → `packaging`,`lintOptions` → `lint` 等) **Bug Fixes — 严重崩溃修复 (P0)** - 非 zip protobuf SVGA 在 DEFAULT 缓存模式下 inflate 后未写入 `movie.binary`,导致缓存完全失效、每次重新下载 - `SVGASoundManager` 所有方法统一 `synchronized(lock)` 保护,修复 `release()` 与 `load()`/`play()` 并发竞态导致 native crash - 音频缓存路径从全局目录改为各 SVGA 独立缓存目录,修复不同 SVGA 中同名音频互相覆盖导致播放错误音频 - 非 zip protobuf SVGA 的 cacheDir 从相对路径改为绝对路径,修复音频缓存写入错误位置且无法被 clearCache 清理 - 音频写入前增加 `mCacheDir.mkdirs()` 防止目录不存在导致 FileNotFoundException - `isCached()` 增加 movie.binary/movie.spec 存在性校验,修复非 zip SVGA 音频缓存目录导致误判已缓存、二次加载失败 - `stopAnimation` 中 `removeAllListeners` 移到 `cancel()` 之前,杜绝虚假 `onFinished` 回调 - `play()` 增加 FPS/frames ≤ 0 守卫,防止除零崩溃 - `mStartFrame > mEndFrame` 守卫,防止负 duration 导致 `IllegalArgumentException` - matte 索引增加 `index == 0` 边界检查,防止越界崩溃 - matte 层 `saveLayer` 在 matteKey 查找失败时未 restore → 确保始终 restore,防止 canvas save stack 破坏 - `SVGASoundManager` 消除 TOCTOU 竞态 NPE - `inflate()` 增加 `try-finally` 确保 `inflater.end()` 调用,修复 zlib 原生内存泄漏 - 线程锁对象修复 + `@Volatile` 可见性保证 - 网络下载添加 `readTimeout`,改用临时文件缓冲避免大文件 OOM **Bug Fixes — 内存泄漏 / 资源泄漏修复 (P1)** - `dynamicImageGeneration` 改为 `AtomicInteger`,修复非原子自增导致异步下载取消机制失效 - `SGVADrawer` 新增 `clear()` 释放 spritePool / reusableSpriteList,修复 clear 后帧数据无法 GC - `SVGACanvasDrawer.clear()` 增加 `matteSprites.clear()`,释放最后一帧残留 sprite 引用 - FILE 缓存写入增加 per-key 锁 + 原子写入(tmp+rename),修复并发写同一缓存导致文件损坏 - 非 zip protobuf 路径先 inflate 再异步写缓存,避免闭包持有 `bytes` 数组阻止 GC - `SVGAVideoEntity.clear()` 增加 `soundPool.stop(playID)`,修复非 SVGASoundManager 模式下 clear 后音频仍播放 - `setDynamicImage(url)` 复用单例 Handler + 请求前/解码前增加代数检查减少无效下载 - `pauseAnimation()` 增加 `!isAnimating` 守卫,未播放时不触发 `onPause` - `decodeFromSVGAFileCacheKey` 移除多余 `threadPoolExecutor.execute` 包装,减少线程切换 - 修复 `setVideoItem` 多次调用时旧 SVGAVideoEntity 的 Bitmap/SoundPool 未释放 - `clear()` 调用 `stopAnimation(false)` 防止 ValueAnimator 无限循环泄漏 CPU - `onDetachedFromWindow` / `clear()` 清空回调引用,防止 Activity 泄漏 - `post {}` 增加 `isAttachedToWindow` 守卫,防止 view detach 后 Runnable 泄漏 - `SVGADrawable.clear()` 增加 drawer 缓存回收 + dynamicItem 回收 + 立即设置 `cleared = true` - `SVGADynamicEntity.clearDynamicObjects()` 回收动态 Bitmap - `setDynamicImage(bitmap)` 覆盖同 key 时回收旧 Bitmap - URL 下载增加代数计数器,clear 后完成的下载不再写入已清空的 map - `SVGASoundManager.release()` 释放 SoundPool;先置 null 再 release 防止竞态 native crash - `SVGASoundManager.init()` 加 `@Synchronized`,防止并发泄漏 - `SVGASoundManager.load()` 增加 fd 空值守卫 - `drawTextCache` 清理前先 recycle 所有缓存 Bitmap - `shareMatteCanvas` resize 时回收旧 Bitmap - `drawImage` / `drawTextOnBitmap` 中 `paint.alpha` 增加 `coerceIn(0, 255)` - `FileOutputStream` / `FileInputStream` 全部改用 `.use {}`;音频缓存改用 tmp+rename 防止损坏 - `movieItem` / `mPlayCallback` 在 `clear()` 中释放 **Bug Fixes — 功能修复 (P1)** - `decodeFromCacheKey` 增加 `prepare()` 调用,修复 zip SVGA 从缓存加载后音频不播放 - `decodeFromURL` 缓存命中时正确传递 `playCallback`;缓存命中路径增加 try-catch 防止 callback 丢失 - `setupAudios` 重构:音频加载失败时不再挂起动画 - `return` → `return@forEach` 修复 JSON 图片解析提前退出 - `stepToFrame` 增加边界检查防止 `IndexOutOfBoundsException`;除数从 frames 改为 frames-1 修复最后一帧不可达 - `pauseAnimation()` 改为真正暂停(ValueAnimator.pause() + drawable.pause()),新增 `resumeAnimation()` - `onAnimationEnd` 增加旧 animator 防护,防止回调影响当前状态 - `Handler()` → `Handler(Looper.getMainLooper())` 消除非 Looper 线程崩溃 - `setDynamicImage(url)` 添加 `readTimeout = 20s` - `setDynamicText(BoringLayout)` 移除冗余 isBoring 检查 - `HttpURLConnection` 在 `finally` 中 `disconnect()` 释放 socket - 缓存损坏时改用 `deleteRecursively()` 完整清理 - 移除 `cacheLocks.remove()`,防止并发 unzip 竞争 - EOF 判断从 `<= 0` 改为 `== -1`;显式指定 UTF-8 编码 - URL 非 HTTP 连接时显式回调 `failure()` - `totalTime` 零检查从 `toInt() == 0` 改为 `== 0.0` - `onLoadComplete` 改为所有 status 回调,失败音频不再挂起动画 - `setVolume` / `resume()` 中修正 return 作用域和日志文本 **Bug Fixes — 线程安全修复 (P1)** - `cancelled` 改为 `AtomicBoolean`,保证跨线程可见 - `soundCallBackMap` 改为 `ConcurrentHashMap` - `soundPool` / `initialized` / `isAnimating` / `isTextDirty` 添加 `@Volatile` - 移除全局 `sharedPath`,消除多线程共享风险 **Code Cleanup (minSdk 21)** - 移除 API 18/21 版本兼容分支和废弃 `SoundPool` 构造方式 **Performance — 性能优化** - MD5 十六进制编码改用查表法,性能提升约 10 倍 - IO 缓冲区 2048 → 8192,减少 4 倍系统调用 - `inflate()` 输出缓冲区预分配,减少扩容拷贝 - 有界线程池替代 `CachedThreadPool`,防止无限创建线程 - `parserImages` 与 `generateAudioMap` 合并为单次遍历 - `prepare()` 完成后立即释放 protobuf 原始数据 - 渲染层每帧对象复用:sprite list、matteSprites map、Canvas、FloatArray、PorterDuffXfermode - `BooleanArray` 替代 `Array` 消除装箱 - `performScaleType` 脏检查缓存,仅变化时重算 - `drawShape` 移除冗余 `reset()`,alpha 预计算 - 反射 `getDurationScale` Method 缓存,不再每次反射 - `1000/FPS` 改为浮点除法,消除帧率计时误差 - `RGB_565` → `ARGB_8888` 保留透明通道 - `ByteArray.slice()` 改为直接索引,消除装箱开销 - `cacheDir` getter / `isInitialized()` 减少磁盘系统调用 - 音频缓存 key 改用文件大小替代全量 MD5 - `charset("UTF-8")` → `Charsets.UTF_8`,避免每次创建 Charset 查找 - `buildPath()` 对 `Type.keep` early-return + 消除冗余 Path 拷贝 - `data.count()` → `data.size`,避免额外函数调用 --- ``` //历史版本 api "com.gitee.hljdrl:svgaplayer-android:2.6.2.0" ``` #### 2.6.2.0 更新内容 - fix(SVGAVideoShapeEntity.kt--->checkAlphaValueRange): 修复-增加非空判断,部分动画导出后在android平台无法正常解析播放. ``` //历史版本 api "com.gitee.hljdrl:svgaplayer-android:2.6.1.0" ``` #### 2.6.1.0 更新内容 - fix(SVGACache): 修复-带音频的svga动画播放时,svga动画中音频audiokey相同,导致多组动画声音文件缓存命中第一个动画的音频. ### Demo 视频预览 ### Demo 运行效果 | | | |:--------------------------------------------------:|:---:| | 主菜单 | Assets 动画列表 | | | | | 从 Assets 随机加载 | 从网络 URL 加载 | | | | | 从 XML 布局加载 | 动态替换图片 | | | | | 点击区域检测 | 循环/单次播放测试 | | | | | 缓存压力测试 | 动画帧预览 | | | | ### ================================================ ### 以下为原有项目信息 # SVGAPlayer ## 介绍 `SVGAPlayer` 是一个轻量的动画渲染库。你可以使用[工具](http://svga.io/designer.html)从 `Adobe Animate CC` 或者 `Adobe After Effects` 中导出动画文件,然后使用 `SVGAPlayer` 在移动设备上渲染并播放。 `SVGAPlayer-Android` 使用原生 Android Canvas 库渲染动画,为你提供高性能、低开销的动画体验。 如果你想要了解更多细节,请访问[官方网站](http://svga.io/)。 ## 用法 我们在这里介绍 `SVGAPlayer-Android` 的用法。想要知道如何导出动画,点击[这里](http://svga.io/designer.html)。 我们的 aar 包托管在 JitPack 上,你需要将 `JitPack.io` 仓库添加到工程 `build.gradle` 中。 ``` allprojects { repositories { ... maven { url 'https://jitpack.io' } } } ``` 然后,在应用 `build.gradle` 中添加依赖。 ``` compile 'com.github.yyued:SVGAPlayer-Android:latest' ``` [![](https://jitpack.io/v/yyued/SVGAPlayer-Android.svg)](https://jitpack.io/#yyued/SVGAPlayer-Android) ### Parser 单例支持 SVGAParser 单例需要在使用之前初始化, 否则会上报错误信息: `Log.e("SVGAParser", "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")` ### 遮罩支持 请参阅此处 [Dynamic · Matte Layer](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-%C2%B7-Matte-Layer) ### 混淆规则 ``` -keep class com.squareup.wire.** { *; } -keep class com.opensource.svgaplayer.proto.** { *; } ``` ### 放置 svga 文件 SVGAPlayer 可以从本地 `assets` 目录,或者远端服务器上加载动画文件。 ### 使用 XML 你可以使用 `layout.xml` 添加一个 `SVGAImageView`。 ```xml ``` 在 XML 中,允许定义以下这些标签: #### source: String 用于表示 svga 文件的路径,提供一个在 `assets` 目录下的文件名,或者提供一个 http url 地址。 #### autoPlay: Boolean 默认为 `true`,当动画加载完成后,自动播放。 #### loopCount: Int 默认为 `0`,设置动画的循环次数,0 表示无限循环。 #### ~~clearsAfterStop: Boolean~~ 默认为 `false`,当动画播放完成后,是否清空画布,以及 SVGAVideoEntity 内部数据。 不再推荐使用,开发者可以通过 clearAfterDetached 控制资源释放,或者手动通过 SVGAVideoEntity#clear 控制资源释放 #### clearsAfterDetached: Boolean 默认为 `false`,当 SVGAImageView 触发 onDetachedFromWindow 方法时,是否清空画布。 #### fillMode: String 默认为 `Forward`,可以是 `Forward`、 `Backward`、 `Clear`。 `Forward` 表示动画结束后,将停留在最后一帧。 `Backward` 表示动画结束后,将停留在第一帧。 `Clear` 表示动画播放完后,清空所有画布内容,但仅仅是画布,不涉及 SVGAVideoEntity 内部数据。 ### 使用代码 也可以使用代码添加 `SVGAImageView`。 #### 创建一个 `SVGAImageView` 实例 ```kotlin SVGAImageView imageView = new SVGAImageView(this); ``` #### 声明一个 `SVGAParser` 单例. ```kotlin parser = SVGAParser.shareParser() ``` #### 初始化 `SVGAParser` 单例 必须在使用 `SVGAParser` 单例前初始化, ``` SVGAParser.shareParser().init(this); ``` 否则会上报错误信息: `Log.e("SVGAParser", "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")` 你也可以自行创建 `SVGAParser` 实例。 #### 创建一个 `SVGAParser` 实例,加载 assets 中的动画。 ```kotlin parser = new SVGAParser(this); // 第三个为可缺省参数,默认为 null,如果设置该方法,则内部不在处理音频的解析以及播放,会通过 PlayCallback 把音频 File 实例回传给开发者,有开发者自行控制音频的播放与停止。 parser.decodeFromAssets("posche.svga", object : SVGAParser.ParseCompletion { // ... }, object : SVGAParser.PlayCallback { // The default is null, can not be set }) ``` #### 创建一个 `SVGAParser` 实例,加载远端服务器中的动画。 ```kotlin parser = new SVGAParser(this); // 第三个为可缺省参数,默认为 null,如果设置该方法,则内部不在处理音频的解析以及播放,会通过 PlayCallback 把音频 File 实例回传给开发者,有开发者自行控制音频的播放与停止。 parser.decodeFromURL(new URL("https://github.com/yyued/SVGA-Samples/blob/master/posche.svga?raw=true"), new SVGAParser.ParseCompletion() { // ... }, object : SVGAParser.PlayCallback { // The default is null, can not be set }) ``` #### 创建一个 `SVGADrawable` 实例,并赋值给 `SVGAImageView`,然后播放动画。 ```kotlin parser = new SVGAParser(this); parser.decodeFromURL(..., new SVGAParser.ParseCompletion() { @Override public void onComplete(@NotNull SVGAVideoEntity videoItem) { SVGADrawable drawable = new SVGADrawable(videoItem); imageView.setImageDrawable(drawable); imageView.startAnimation(); } @Override public void onError() { } }); ``` ### 缓存 `SVGAParser` 不会管理缓存,你需要自行实现缓存器。 #### 设置 HttpResponseCache `SVGAParser` 依赖 `URLConnection`, `URLConnection` 使用 `HttpResponseCache` 处理缓存。 添加代码至 `Application.java:onCreate` 以设置缓存。 ```kotlin val cacheDir = File(context.applicationContext.cacheDir, "http") HttpResponseCache.install(cacheDir, 1024 * 1024 * 128) ``` ### SVGALogger 更新了内部 log 输出,可通过 SVGALogger 去管理和控制,默认是未启用 log 输出,开发者们也可以实现 ILogger 接口,做到外部捕获收集 log,方便排查问题。 通过 `setLogEnabled` 方法设置日志是否开启。 通过 `injectSVGALoggerImp` 方法注入自定义 ILogger 实现类。 ```kotlin // 默认情况下,SVGA 内部不会输出任何 log,所以需要手动设置为 true SVGALogger.setLogEnabled(true) // 如果希望收集 SVGA 内部输出的日志,则可通过下面方式获取 SVGALogger.injectSVGALoggerImp(object: ILogger { // 实现相关接口进行接收 log }) ``` ### SVGASoundManager 新增 SVGASoundManager 控制 SVGA 音频,需要手动调用 init 方法进行初始化,否则按照默认的音频加载逻辑。 另外通过 SVGASoundManager#setVolume 可控制 SVGA 播放时的音量大小,范围值在 [0f, 1f],默认控制所有 SVGA 播放时的音量, 而且该方法可设置第二个可缺省参数:SVGAVideoEntity,表示仅控制当前 SVGA 的音量大小,其他 SVGA 的音量保持不变。 ```kotlin // 初始化音频管理器,方便管理音频播放 // 如果没有初始化,则默认按照原有方式加载音频 SVGASoundManager.init() // 释放音频资源 SVGASoundManager.release() /** * 设置音量大小,entity 默认为空 * 当 entity 为空,则控制所有通过 SVGASoundManager 加载的音频音量大小,即包括当前正在播放的音频以及后续加载的音频 * 当 entity 不为空,则仅控制该实例的 SVGA 音频音量大小,其他则不受影响 * * @param volume 取值范围为 [0f, 1f] * @param entity 即 SVGAParser 回调回来的实例 */ SVGASoundManager.setVolume(volume, entity) ``` ## 功能示例 * [使用位图替换指定元素。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Image) * [在指定元素上绘制文本。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Text) * [在指定元素上绘制富文本。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Text-Layout) * [隐藏指定元素。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Hidden) * [在指定元素上自由绘制。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Drawer) ## APIs 请参阅此处 [https://github.com/yyued/SVGAPlayer-Android/wiki/APIs](https://github.com/yyued/SVGAPlayer-Android/wiki/APIs) ## CHANGELOG 请参阅此处 [CHANGELOG](./CHANGELOG.md)