指纹解锁动画空跑不停止 — 问题排查
日期: 2026-05-08
Trace:/home/zbc/pangu/GeneralAndroid/main/hideapi/tool_new/output/trace/perfetto_1778220614.pftrace
曲线图:captures/animation_curve_full.png
一、现象
指纹解锁时,folme 动画 magazine_translateY 到达目标值后不停止,持续空跑约 1100ms(66帧),每帧值完全不变。
Trace 中对应的动画信息:
Folme 1327 all_in_one_lock_screen Value{valid ValueTargetObject@1768817721{all_in_one_clock}}
magazine_translateY,font_weight,translationY,notification_top,width,height
spring_phy(1,0.18,1)
动画数据摘要:
| 指标 | 值 |
|---|---|
| 起始值 | -326.1008 |
| 目标值 | -304.1231079102 |
| 总位移 | ~22 pixels |
| 有效运动时间 | 942ms (66 frames) |
| 空跑时间 | ~1100ms (66 frames) |
| 总时长 | ~2040ms (132 frames) |
| 浪费帧占比 | 50% |
| 曲线类型 | 过阻尼弹簧,无overshoot |
二、动画曲线

- 上图: 完整曲线,红色区域为空跑帧(值不变)
- 中图: 有效运动阶段放大,前 200ms 完成 90% 位移,后 740ms 收敛最后 10%
- 下图: 速度曲线,峰值 200 units/sec,之后指数衰减
三、根因分析
3.1 问题代码
文件: packages/SystemUI/miuiModules/Keyguard/src/main/java/com/android/keyguard/clock/animation/allinone/AllInOneClockAnimation.kt:455
private val MARGAZINE_TRANSLATEY: ValueProperty<Int> = ValueProperty("magazine_translateY", 0f)
// ^^^
// minVisibleChange = 0f ← BUG对比其他属性:
private val FONT_WEIGHT: ValueProperty<Int> = ValueProperty("font_weight", 1f) // 正确
private val NOTIFICATION_TOP: ValueProperty<Float> = ValueProperty("notification_top", 1f) // 正确3.2 Folme 收敛判断逻辑
文件: miuix/library/folme/src/.../physics/EquilibriumChecker.java
// Line 39-42: 阈值计算
public void init(FloatProperty property, double targetValue) {
mValueThreshold = property.getMinVisibleChange() * THRESHOLD_MULTIPLIER; // 0f * 0.75 = 0
mVelocityThreshold = mValueThreshold * VELOCITY_THRESHOLD_MULTIPLIER; // 0 * 16.67 = 0
mTargetValue = targetValue;
}
// Line 45-49: 收敛判断
public boolean isAtEquilibrium(int easeStyle, double value, double velocity) {
boolean isAt = isAt(value, mTargetValue); // Math.abs(value - target) < 0 → 永远 false
return (easeStyle != EaseManager.EaseStyleDef.SPRING_PHY || isAt)
&& easeStyle != EaseManager.EaseStyleDef.ACCELERATE
&& Math.abs(velocity) < mVelocityThreshold; // velocity < 0 → 永远 false
}3.3 为什么 minVisibleChange = 0 导致不停止
当 minVisibleChange = 0f 时:
mValueThreshold = 0f * 0.75f = 0fmVelocityThreshold = 0f * (1000/60) = 0fisAt()要求Math.abs(value - target) < 0f→ 数学上永远不成立(除非完全相等)- 速度判断要求
Math.abs(velocity) < 0f→ 同样永远不成立
弹簧物理模拟中,由于浮点精度问题,value 和 velocity 几乎不可能精确等于 0,导致动画永远无法通过收敛检查。
3.4 为什么最终还是停了
文件: miuix/library/folme/src/.../internal/FolmeCore.java:28, 309
private static final long LONGEST_DURATION_NANOS = 10000000000L; // 10秒硬超时
static boolean isAnimRunning(...) {
boolean isRunning = !checker.isAtEquilibrium(easeStyle, value, velocity);
if (isRunning && totalTimeNanos > LONGEST_DURATION_NANOS) {
isRunning = false; // 10 秒超时强制结束
}
return isRunning;
}但在此 trace 中,动画在约 2 秒时结束(可能是因为该动画属性是批量动画组的一部分,其他属性如 translationY(minVisibleChange=1f)正常收敛后触发了整组结束)。
3.5 为什么 minVisibleChange = -1 时不会有问题
// FolmeCore.java:110-111
if (data.property.getMinVisibleChange() == -1f) {
data.property.setMinVisibleChange(target.getMinVisibleChange(data.property.getName()));
}如果传 -1f(默认值),folme 会通过 IAnimTarget.getMinVisibleChange() 自动推断合适的阈值。但传 0f 跳过了这个逻辑,被视为”有效值”。
四、动画参数
spring_phy(1, 0.18, 1)
| 参数 | 含义 | 值 |
|---|---|---|
| damping | 阻尼比 | 1 (临界阻尼) |
| response | 响应时间 | 0.18s |
| bounce | 弹跳 | 1 |
临界阻尼 (damping=1) 意味着无 overshoot,值单调趋近目标。这也解释了为什么曲线是纯指数衰减形态。
五、修复方案
将 minVisibleChange 从 0f 改为 1f(像素级别的最小可见变化):
// AllInOneClockAnimation.kt:455
// Before:
private val MARGAZINE_TRANSLATEY: ValueProperty<Int> = ValueProperty("magazine_translateY", 0f)
// After:
private val MARGAZINE_TRANSLATEY: ValueProperty<Int> = ValueProperty("magazine_translateY", 1f)修复后阈值:
mValueThreshold = 1f * 0.75f = 0.75f(距目标 0.75px 以内视为到达)mVelocityThreshold = 0.75f * 16.67 = 12.5f(速度低于 12.5 px/s 视为静止)
预估效果:动画将在 ~300ms 时判定收敛(位移 <0.75px 且速度 <12.5px/s),节省约 700ms + 1100ms 空跑 = 1800ms 的无效帧刷新。
六、影响范围
仅影响 all_in_one_clock 动画组中的 magazine_translateY 属性:
- 指纹解锁时锁屏时钟画报的 Y 轴位移动画
- 修复后视觉效果无任何差异(0.75px 位移不可见)
七、排查工具链
本次排查使用的工具:
- FolmeTracer 打点 — 获取动画 ID、参数、开始/结束时间
- Perfetto Trace — 时间线上定位动画 slice,与系统帧对齐
- folme_design log — 逐帧
magazine_translateY值,生成曲线 - Python matplotlib — 可视化曲线,明确看到收敛点和空跑段
排查流程:
FolmeTracer 发现动画时长异常 → Perfetto 定位时间段 →
folme_design log 提取逐帧值 → 绘制曲线发现空跑 →
分析 EquilibriumChecker 代码 → 定位 minVisibleChange=0f 根因