外观
跨屏动画的时序与首帧接管
本页的协调协议是可讨论的设计方案。实际系统回调、显示栅栏和转场完成信号应按目标 Android / ROM 版本核对。
1. 源屏飞出、目标屏飞入,怎样控制开始时机?
**标准回答:**我会由一个协调者管理同一次转场,先创建唯一的 transitionId,再让两端准备快照、图层、尺寸和动画参数。两端分别报告就绪后,协调者下发统一的开始时刻和运动参数。这样可以把“准备耗时不同”与“动画开始后进度不同”分开处理。
同一设备的不同进程可以使用约定好的单调时钟,例如 uptimeMillis;不能把墙上时钟、纳秒值和毫秒值混用。统一开始时刻通常需要预留传播和调度余量,但余量应依据实测,而不是固定认为延迟 16ms 就一定足够。
**追问:两个 Handler.post 同时执行不就行了吗?**它们进入不同线程队列,受到线程忙碌、IPC 和显示调度影响,执行和呈现时刻都可能不同。统一时间基准能够减少相位偏差,但仍需观测最终的显示结果。
2. 统一开始时间,是否等于两个屏幕每一帧完全同步?
**标准回答:**不等于。开始时间是动画计算的基准,各显示端仍按自己的 VSync、刷新率和提交时机采样。比较稳健的做法是用经过的时间计算动画进度,例如 progress = clamp((now - startTime) / duration),再应用相同的插值函数,而不是每收到一帧都把进度固定加一。
如果源屏是 60Hz、目标屏是 90Hz,两端采样点和帧数本来就不同。目标是内容在相同真实时间处于接近的运动位置,以及交接阶段没有明显跳变;不应承诺普通软件协调能让两块不同硬件每次扫描严格同时发生。
| 刷新率 | 一个名义刷新周期 |
|---|---|
| 60Hz | 约 16.67ms |
| 90Hz | 约 11.11ms |
| 120Hz | 约 8.33ms |
**追问:某一端掉了一帧怎么办?**下一次回调应按当前时间算进度,避免整体落后一帧。明显持续落后时还需定位线程、GPU 或合成瓶颈,不能只靠跳过计算掩盖问题。
3. 什么时候撤掉快照,让真实窗口接管?
**标准回答:**应在目标真实窗口满足定义好的接管条件后,再做可见性交接。条件至少包括目标任务和窗口身份正确、目标显示和 bounds 已更新、有效画面已经产生,并且该画面符合这次转场的配置。动画播放结束只是一个条件,不能直接替代目标内容就绪。
为了减少竞态,可以让图层显隐和替身清理在一次相关事务或系统同步转场路径中提交;若需要证明用户已经看到某一帧,还要追踪对应的呈现事件或栅栏语义。公共应用 API、系统权限 API 和厂商内部回调的可用能力不同,不能把某个隐藏接口作为所有应用都能使用的方法。
**追问:onResume、onDraw、onPreDraw 能表示目标已经上屏吗?**不能。它们分别接近组件状态与应用绘制流程中的阶段,后面还可能有 RenderThread、GPU、缓冲区提交、合成和显示等待。它们可以作为部分就绪信号,但不能单独证明物理呈现。
4. Transaction.apply 和 committed 回调可以当成上屏完成吗?
**标准回答:**不能这样直接等同。apply 提交图层事务,不意味着屏幕已经显示这些更新。API 33 的 addTransactionCommittedListener 表示事务已应用、更新准备被呈现,也不表示相关缓冲区已释放或用户已经看到最终像素。判断显示完成时要区分提交、应用、合成与实际呈现。
同一个协调器可以记录这些信号的时间戳,但日志字段应使用准确名字,例如 transactionSubmitted、transactionCommitted、targetFramePresented,避免全部记作 animationFinished,导致故障时看不出卡在哪一层。
**追问:如何证明接管后没有闪屏?**记录对应转场和图层标识,将目标端 buffer / FrameTimeline 与屏幕录像中的异常帧关联,检查替身撤掉前后真实窗口的尺寸和画面。只有应用层时间戳,没有显示证据,结论仍不完整。