Repository navigation
MusicPlayer v2.22.19
- 版本号:versionName 2.22.19 / versionCode 60
- 日期:2026-09-18
- 本版主题:前台 CPU 占用优化——修复「炫彩流光文字」动画导致的每帧全视口重绘(实测 −26.5%)
需求背景
本版针对前台播放场景的 CPU / 内存占用做专项排查与优化。
真机(arm64-v8a)采样结论:
| 状态 | CPU 占用 |
|---|---|
| 前台亮屏(暂停) | 173.4% |
| 前台亮屏(播放) | 176.2% |
| 后台(HOME) | 14.8% |
| 锁屏 | 5.7% |
- 前台高占用与是否播放无关(差 2.8 点),说明不是音频解码开销。
dumpsys gfxinfo:稳定 121fps、无掉帧;每帧 CPU 侧约 22ms(高刷屏每帧预算 8.3ms)、GPU 仅 3ms
→ 瓶颈在 CPU 侧渲染管线。- 内存无泄漏:连续切歌 16 次 PSS 471→551→515MB(回落),导入 1387 首仅 +12MB,线程 75→77。
归因过程(关键:第一次判断是错的)
第一轮判断(v2.22.18,已证伪)
按"毛玻璃很贵"的直觉,把整屏模糊从 #bg-veil 的 backdrop-filter 迁到壁纸层 filter,
并下调各处模糊半径、加 contain: layout paint。实测只降约 7%,与预期严重不符。
第二轮:CDP 逐项 A/B 归因(决定性)
经 adb forward 接 WebView 的 webview_devtools_remote_<pid>,用 Runtime.evaluate
注入覆盖 CSS,在同一进程、同一首歌、同一次会话里逐项开关。
共 5 轮、47 个对照场景(工具:_perf/cdp_attr.mjs,Node 22 原生 WebSocket,零第三方依赖)。
| 场景 | CPU 均值 | Δ | 判读 |
|---|---|---|---|
| 基线 | 163.7% | 0 | — |
| 停 19 个列表项标题的 fx-flow | 136.4% | −31.5 | 唯一显著项 |
| ↳ 复测(重复性) | 136.6% | −31.3 | 复现性 ±0.2 |
| 停跑马灯环 ring-run | 170.5% | +7.5 | 无成本 |
| 停歌词流逝 lyric-flow | 169.5% | +1.6 | 无成本 |
| 停顶栏 / 栏头 / 数值类 | 164.6~166.6% | −1.3~−3.3 | 噪声 |
| 关全部 transition | 163.3% | +3.1 | 无成本 |
| 关全部 backdrop-filter | 153.2% | −10.5 | 约 −6%,非主因 |
| 关 filter / 壁纸模糊 | 165.5% | +1.8 | 无成本 |
| 关阴影 | 161.7% | −2.0 | 噪声 |
根因:播放列表 19 个曲目标题用 background-clip:text + 动画 background-position
实现「炫彩流动文字」(@keyframes fx-flow)。该动画不可合成到 GPU 合成线程,
每一帧都要在主线程重新栅格化文字渐变色。
非线性发现:顶栏 / 栏头 / 数值三项各自单停只值 1.3~3.3 点,但连同列表项标题一起停掉却 −54.2 点
("关全部动画"上限 −83.5)。说明成本主体是「这一帧到底有没有跨视口重绘」——
只要视口内任意一处持续变化,整条管线(主线程样式/布局/绘制 + 合成 + 所有 backdrop-filter
区域重新快照并模糊)就每帧全量运转;此时增删单个元素的边际成本很小。
实现
-
www/style.css:6 处fx-flow动画的时间函数linear→steps(240, end):/* 改前 */ animation: fx-flow var(--fx-speed, 10s) linear infinite; /* 改后 */ animation: fx-flow var(--fx-speed, 10s) steps(240, end) infinite;
渐变更新率由约 121 次/秒降到约 8 次/秒(
--fx-speed默认 10s 时约 24 次/秒)。
动画外观不变:位移速度仍约 70px/s,每步仅约 1% 背景宽(235px 标题约 7px),
柔和渐变下肉眼无差别。语法注意:steps()与linear互斥,不能同写(否则整条声明失效)。 -
www/player.js:v2.22.18 的性能改造保留(壁纸模糊迁移、coverTick30fps 闸门 + 页面隐藏门控、
通知推送四要素去重、EQ 渐变对象按高度量化缓存),并修正其中已证伪的注释。 -
web-demo/style.css、web-demo/demo.js:同步同一套改造。 -
清理:
www/_perf_bak_20260918/(会被cap copy打进 APK)已移出到项目根。 -
android/app/build.gradle:versionCode 59 → 60,versionName 2.22.18 → 2.22.19。 -
www/index.html:设置页版本号 v2.22.19。
验证
修复前预演(CDP A/B)
| 场景 | CPU | Δ |
|---|---|---|
| 基线 | 164.5% | 0 |
| 全部 fx-flow 元素加 steps(240, end) | 121.3% | −43.2(−26%) |
| 仅列表项标题完全停掉(对照) | 137.6% | −26.9 |
量化后的成本已与"完全删掉动画"持平 —— 拿到全部收益、零视觉损失。
修复后实测(真机,同方法)
| 场景 | v2.22.18 | v2.22.19 | 变化 |
|---|---|---|---|
| 前台亮屏播放(基线) | 164.5% | 120.9% | −26.5% |
| ↳ 基线复测 | 167.9% | 133.6% | −20.4% |
| 关全部动画(下限对照) | 79.5% | 98.0% / 86.5% | 差距收窄至约 23 点 |
与预演预测的 −26% 吻合。网页侧核对计算值确认修复生效:
TIMING_FN: {"titleName":"fx-flow","titleTF":"steps(240)","titleDur":"30s",
"clockName":"fx-flow","clockTF":"steps(240)"}
构建与归档
- cap copy →
assembleDebugBUILD SUCCESSFUL(18s + 13s 两次,JDK 21)。 - 装机:
adb install -r -d→ Success;dumpsys package确认 versionCode=60 / versionName=2.22.19。 - 归档:
python _release/archive_release.py 2.22.19 60
(source 快照 32.6MB + APK 本版/回滚基线 + source zip + website zip)。 - webdemo 包:
python _release/pack_webdemo.py 2.22.19 60(本版新增工具)。 - 回滚基线:
APK/app-v2.22.18-code59.apk(亦在<归档>/_apk_backup/)。
装机记录
- 覆盖安装成功,用户数据保留。
- 冷启动正常,无
FATAL/AndroidRuntime异常;播放正常。 - 修复后新会话实测前台 CPU 稳定在 100~119%。
测量局限(如实说明)
- 帧率不可锁:部分厂商 ROM 忽略
settings put system peak_refresh_rate,设备稳定在约 121fps。
若能在 60Hz 下运行,CPU 占用预计近似减半——这是一条尚未利用的优化路径,但系统层无可用接口。 - 存在状态依赖:同一组覆盖条件在两次独立测量中最大差 38 点(67.9% vs 106.0%),
疑与温度、调度、播放进度有关。故只采纳多轮重复一致的项;单次极端值不予采信。 - CPU 口径:为「相对单核」百分比,8 核满载为 800%;含进程内所有线程。
- GPU 归因有限:
dumpsys gfxinfo的 GPU 耗时(约 3ms)未完整覆盖 WebView 自身渲染管线。
构建备忘
同 v2.22.13:cap copy android → JAVA_HOME=<JDK 21> +
ANDROID_HOME=<ANDROID_SDK> + NODE_OPTIONS="--use-system-ca" → gradlew assembleDebug。
归因工具复现方式:
ADB=<adb路径>
PID=$("$ADB" shell pidof com.musicplayer.app | tr -d '\r')
"$ADB" forward tcp:9222 localabstract:webview_devtools_remote_$PID
NODE_OPTIONS="--use-system-ca" node _perf/cdp_attr.mjs com.musicplayer.app "$PID" run