这篇补一个很容易被启动速度和流畅度盖住的话题:交互首帧响应。它关心用户刚操作完之后,屏幕多久给出第一帧反馈;页面完整加载和动画后半段稳定性要用其他指标衡量。
点一个按钮,多久出现按压态;点微信会话,多久看到会话页第一帧开始动;手指滑动列表,多久看到内容跟着手指移动。这些问题和启动慢有关,也和掉帧有关,但分析口径要单独拎出来。
本文用 Perfetto 拆这段时间:从 InputReader read_time 起算,到 App 收到事件,再到第一帧 present。主指标叫 input_to_present_ms,只度量 input read 到关联帧 present;它不覆盖触控 IC、驱动上报前延迟、显示扫描和面板响应。读完之后,你应该能给一个点击或滑动场景定义起点、终点、采集配置、SQL 指标和 App 侧补充 marker。
文章会把响应慢拆成 read/dispatch、handling、ACK→first frame、滑动跟手四个独立指标,分别对应 InputReader/InputDispatcher、App 主线程、RenderThread/SurfaceFlinger 和滑动场景的位移延迟;给出对应的 Trace 采集配置、Perfetto UI 上要先看哪几条轨道、点击和滑动两类场景的起点终点定义、用 SQL 量化 input_to_present_ms 的写法;最后归纳 Read/dispatch 慢、Handling 慢、ACK 快但首帧慢、ANR 与亚 ANR、滑动不跟手这五条常见归因路径,以及高速相机和 Perfetto 各自的边界。
本文只看交互后的第一帧
旧的 Systrace 响应速度系列已经讲过广义响应慢:启动、页面跳转、亮灭屏、解锁、系统繁忙、Binder 等场景。流畅度系列讲连续帧是否稳定。Perfetto 7 和 8 又把 MainThread、RenderThread、Vsync、SurfaceFlinger 的帧路径拆过一轮。
这一篇只补一段:
1 | 用户输入 |
本文把主指标统一叫 input_to_present_ms:从 android_input_events.read_time 到关联帧 present,SQL 字段对应 end_to_end_latency_dur。它回答的是“点了以后多久有第一帧反馈”,不是“整个页面多久加载完”,也不是“动画播放过程中有没有掉帧”。只有 associated frame 可用且 is_speculative_frame = false 时,它才是强证据;否则报告应降级为 estimated_input_to_present_ms 或不输出。
如果用 event_time 或业务 marker 作为起点,报告要另列字段,不能和 input_to_present_ms 混在一起比较。
响应慢要拆成四个指标
响应慢的讨论经常混在一起。开始分析前,先把同一次交互拆成四个指标。
| 指标 | 起点 | 终点 | 适合回答的问题 |
|---|---|---|---|
| Dispatch latency | InputDispatcher 发出事件 | App 收到事件 | 系统分发和 InputChannel 是否慢 |
| Handling latency | App 收到事件 | App 发出 ACK | App input 处理是否慢 |
input_to_present_ms |
input read (read_time) |
关联帧 present | 用户多久看到第一帧反馈 |
| Completion latency | 业务触发点 | 页面或动画稳定 | 整个操作多久完成 |
用户最敏感的是 input_to_present_ms。第一次反馈快,后续内容分批出来,用户会觉得系统有响应;第一次反馈慢,后续动画再稳定,也容易被感知成“点了没反应”。
点击和滑动的终点也不一样:
- 点击按钮:终点可以是按压态 present、弹窗第一帧 present、页面转场第一帧 present。
- 点击会话:终点可以是会话页进入动画第一帧 present,也可以是第一条消息内容绘制完成。
- 手指滑动:终点应该是内容第一次发生可见位移的 present。
Perfetto 能帮你定位系统内部时间,但“第一帧是否真的有视觉变化”经常需要 App marker 或外部视觉工具校准。
还要区分三个“输入时间”:
event_time: 输入事件发生时间,比read_time更靠前,但仍不是触控固件、电信号或外部可见起点。read_time: InputReader 读到事件的时间,android_input_events的 end-to-end 字段从这里算到 present。dispatch_ts/receive_ts: InputDispatcher 发出事件、App 接收事件的时间,用来拆系统分发和 App 处理。
报告里不要混着写“从点击开始”。如果你用 read_time 起算,就写 input read;如果你用业务 click handler 起算,就写 App marker。起点不同,数字不能直接比较。
在写 SQL 前,还要把本次场景的对象字典固定下来。至少包括目标包名、InputChannel、目标窗口、目标 layer、业务 marker 和预期终点:
1 | scenario_id: conversation_click |
自动化脚本可以先按包名和窗口名生成候选对象,但报告只使用人工确认过的对象。否则一次 trace 里有多个窗口、弹窗、SurfaceView 或 overlay 时,SQL 很容易把输入事件关联到错误的 layer。
交互场景也要版本化成 schema。点击场景的最小契约可以这样写:
1 | schema_version: input_response_v1 |
滑动场景要把终点从“帧 present”细化成“内容第一次可见位移的 present”:
1 | schema_version: input_response_v1 |
Trace 采集配置
采集分两层:
| preset | build/场景 | 数据源 | 输出口径 |
|---|---|---|---|
lab_input_debug |
debuggable system、实验室短窗口 | android.input.inputevent、FrameTimeline、sched、App marker、少量系统 category |
input round trip + input_to_present_ms |
field_input_response_low_overhead |
user/field,平台受控采集 | App marker、FrameTimeline/sched 可得信号、少量 ATrace、trigger metadata | app_marker_to_present_ms,不输出 input_to_present_ms |
本地或实验室分析可以打开更完整的 input 数据源。这份短 Trace 配置用于复现点击/滑动响应,重点看 android.input.inputevent 和 atrace_categories: "input":
1 | buffers { |
android.input.inputevent 面向 debuggable system build,也就是 userdebug/eng 这类受控环境。TRACE_MODE_TRACE_ALL 会记录系统处理的输入事件,只适合本地设备和测试。
field/user build 上不要依赖 android.input.inputevent。如果 debuggable/受控环境里要降低隐私风险,也只能用 TRACE_MODE_USE_RULES、严格匹配规则、secure/IME/spy window 审查和访问控制;规则模式不能把它变成普通线上采集能力。Input 事件可能同时发给前台窗口、SystemUI、IME 或 spy window,match_any_packages / match_all_packages 设计不当会扩大采集范围。
field preset 要把 secure window、IME、spy window、多目标分发、脱敏、留存周期和访问控制写进评审项。
普通 App 不能自己开启系统 ftrace、FrameTimeline 或 android.input.inputevent session。<profileable android:shell="true" /> 适合让 release 包开放 CPU/内存 profiler 等本地 profiling;Android 12+ App tracing 默认可用于所有 App,Android 11 及以下的 android.os.Trace 仍要注意 profileable/debuggable 边界。
但它不会让普通 user build App 获得系统 inputevent、ftrace 或 SurfaceFlinger session 的自启动权限。
如果设备或版本没有 android.input.inputevent,后面的 android_input_events round-trip SQL 基本不可用,只能用 atrace input、App 主线程、FrameTimeline、RenderThread、SurfaceFlinger、App marker 和外部视频拼证据。报告要记录用了哪个 fallback、缺哪些字段、结论等级如何降级。
fallback 要提前固定:
| 缺失信号 | 还能输出什么 | 不能输出什么 |
|---|---|---|
缺 android.input.inputevent |
App marker 到 present、FrameTimeline/调度上下文 | input_event_id、read/dispatch/handling/ack、input_to_present_ms |
| 缺 FrameTimeline | input round trip、App marker、线程和 SurfaceFlinger 线索 | associated frame、input_to_present_ms |
| SurfaceView/游戏/视频 | App marker、layer、外部视频、厂商工具 | 仅凭 FrameTimeline 认定首个可见变化 |
| data loss / clock sync 异常 | 残留证据里的局部观察 | 强结论和跨数据源精确耗时 |
FrameTimeline 也要写边界:它需要 Android 12 及之后版本。官方文档当前说明 SurfaceView 不受支持,所以视频、游戏、地图这类场景不能只靠 FrameTimeline 判断首帧可见变化,要结合 layer、App marker、外部视频或厂商图形工具校准。
input_to_present_ms 只能说明某个输入事件关联到某帧 present;是否为目标 UI 的首个可见变化,还要由 target layer、业务 marker、scroll counter、截图/视频或领域工具确认。frame_id 有值也不是视觉变化的充分证明。
本地深入排查时,还可以配合 Winscope 的 SurfaceFlinger layers/transactions 数据源看窗口和 layer 状态。但这类数据源可能很重,尤其 MODE_ACTIVE、buffer、HWC 等 trace flag 会明显增加数据量,只适合短时间实验室 Trace。
线上现场仍然要依靠第 13、15、17 篇里的受控 preset、trigger、业务 marker 和隐私规则。
采完 trace 后先跑数据质量检查。input 分析高度依赖时间顺序和 FrameTimeline 关联,ftrace 或中央 buffer 丢数据时,报告要降级:
1 | SELECT name, idx, severity, source, value |
这些异常要按 inputevent、FrameTimeline、ftrace/sched、central_buffer、clock_sync 分组输出;任一关键组异常,都要降低 input_to_present_ms 的证据等级。
linux.sys_stats 能补 CPU 频率采样,但频率判断仍要看 idle、cluster/cpuset、uclamp、thermal、窗口前一条 freq 状态,以及目标线程是否真的在对应 CPU/cluster 上运行。这个 preset 默认不打开 IRQ、softirq、workqueue;怀疑 hardirq、kworker 或 softirq 抢占时,要另开短窗口专项 preset。
IRQ/softirq/workqueue 只进短窗口专项:irq/irq_handler_entry、irq/irq_handler_exit、irq/softirq_entry、irq/softirq_exit、irq/softirq_raise、workqueue/workqueue_execute_start、workqueue/workqueue_execute_end。采完必须检查 ftrace overrun/dropped,不能把这些事件放进默认 field preset。
Perfetto UI 里先看哪几条轨道
一次点击或滑动响应,建议按这个顺序看:
- InputReader / InputDispatcher:确认事件是否读到,是否派发给目标窗口。
- App 主线程:确认事件是否到达,收到后是否马上 Running。
Choreographer#doFrame:确认状态变化是通过Input、Insets Animation、Animation、Traversal还是Commit推进到下一帧。input receive/ACK 不一定发生在 doFrame 里,batched input 也可能通过CALLBACK_INPUT消费。- RenderThread:确认 UI 线程提交后,渲染线程有没有卡在
syncFrameState、DrawFrame、dequeueBuffer、queueBuffer等阶段。 - FrameTimeline:确认关联帧的 expected/actual、token、
layer_name和帧关联方式。 - SurfaceFlinger:确认 buffer 是否及时 latch,是否因为 HWC/GPU composition、acquire/release/present fence 或其他 layer 影响最终上屏。
- CPU scheduling/frequency:确认主线程、RenderThread、InputDispatcher 有没有 Runnable 等待、频率不足、迁核异常。
分析时不要从最长的 slice 开始猜。先把输入事件和第一帧反馈连起来,再判断时间花在哪一段。
点击场景:从 DOWN/UP 到第一帧
点击分析要先选起点。不同产品对“点击响应”的定义不同。
| 场景 | 推荐起点 | 推荐终点 |
|---|---|---|
| 按钮按压态 | ACTION_DOWN 或 input read | 按压态第一帧 present |
| 点击确认 | ACTION_UP 或 click handler | 目标 UI 第一帧 present |
| 页面跳转 | click handler / 业务 marker | 转场第一帧 present |
| 内容可用 | click handler / 业务 marker | 首屏内容 marker |
以“点会话进入聊天页”为例,Perfetto 里按这几步看:
- 在
android_input_events或 InputDispatcher 轨道上找到对应 ACTION_UP。 - 看 dispatch 到 App receive 的耗时,判断系统分发是否慢。
- 看 App 主线程收到事件后是否被及时调度。
- 看 click handler 里是否有 Binder、IO、锁等待、同步布局、图片解码。
- 看
startActivityBinder、Activity launch、窗口创建、focus 切换、starting window、transition 和目标 layer 可见性是否插入等待。 - 看下一次
Choreographer#doFrame是否把状态推进到Animation、Traversal或Commit。 - 看 RenderThread、FrameTimeline 和 SurfaceFlinger,确认关联帧是否真的 present。
- 回到 App marker 和对象字典,确认这一帧是否就是用户可见的会话页第一帧。
这里最容易误判的一点是:Perfetto 可能显示 App 画了一帧,但这一帧没有视觉变化。App actual timeline 的结束点是 App 完成并提交帧,不等于用户已经看到。
确认首帧反馈时,要回到 android_input_events.end_to_end_latency_dur 的关联帧、SurfaceFlinger actual/display frame、layer_name、token/flow 和外部证据。没有业务 marker 时,分析者只能靠 UI 截图、layer 名称或外部视频判断。
事件契约先写清,再落到代码里:
| 事件 | 类型 | 线程 | 语义 | 报告字段 | 隐私 |
|---|---|---|---|---|---|
Conversation#Click |
slice | UI thread | click handler 执行窗口 | business_marker_ts、click_handler_dur |
不带会话 id |
Conversation#TransitionStart |
short slice milestone | UI thread | 业务认为转场开始 | transition_marker_ts |
不带页面参数 |
Conversation#FirstContentDrawn |
short slice milestone | UI thread / render callback | 首屏内容业务完成候选 | content_marker_ts、content_marker_seen |
不带内容文本 |
Feed#ScrollOffsetY |
counter | UI thread | 列表可见位移 | first_offset_change_ts、offset_delta_px |
只记录数值 |
Conversation#FirstContentDrawn 属于 business_state_marker,不是 visual_endpoint。最终报告必须写 present_ts、associated_frame_id、layer_name、association_method;没有 present 关联时,只能写 content_marker_seen=true,不能输出 input_to_present_ms。
下面的代码用于给会话点击场景补三个业务点。读者重点看命名:事件名固定,动态 ID 不进入 trace section。
1 | fun onConversationClick(conversationId: String) { |
这三个 marker 分别对应点击处理、转场开始、首屏内容完成。后两个是 milestone 风格的短 slice,报告只取 ts,不把 dur 当业务耗时;需要严格 instant、参数或 flow 时,用 Perfetto SDK Track Event。Perfetto 负责告诉你系统时间,marker 负责告诉你业务语义;终点仍要取关联帧的 present。
android.os.Trace / ATrace 适合放稳定 slice 名和 counter。动态的会话 id、页面 id、实验分组不要拼进 section 名;需要参数、flow 或跨线程 track 时,用第 13 篇的 Perfetto SDK Track Event,或者把业务元数据写进同一个 case 包。
marker 只是业务状态,present 时间来自关联帧。报告里要把 business_marker_ts、associated_frame_id、present_ts、association_method 分开写。
滑动场景:从第一个有效 MOVE 到内容位移
滑动响应比点击更容易混淆。用户手指刚动时,系统会收到很多 MOVE,但 App 可能还没有越过 touch slop,也可能因为事件合并、采样和 Vsync 节奏,只有某一帧才真的产生内容位移。
推荐口径是:
1 | 第一个业务有效 ACTION_MOVE -> 内容第一次可见位移的 present |
分析步骤:
- 找到 ACTION_DOWN 后的 MOVE 序列。
- 排除 touch slop 内的小幅 MOVE,选出业务认为开始滑动的 MOVE。
- 看 App 主线程是否收到这次 MOVE,是否及时 ACK。
- 看
Choreographer#doFrame的Input阶段是否更新 scroll offset。 - 看下一帧是否提交给 RenderThread。
- 看 FrameTimeline present 时间。
- 用 App counter 或外部视频确认内容是否发生位移。
Perfetto 自己不总能知道“内容移动了多少”。自研 App 可以加一个轻量 counter。规则是:只在 offset 值变化时记录,每帧最多一次;报告同时写 first_effective_move_ts、first_offset_change_ts、offset_delta_px、touch_slop_px 和 sample_policy。
1 | recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { |
android.os.Trace.setCounter(String, long) 从 API 29 起可用;低 minSdk 项目要用 AndroidX tracing 或自己加版本保护。大范围 scroll offset 可用 SDK Track Event counter 或分桶值。这个 counter 不要放在每次布局细节里打爆,只记录用户可见状态,例如 scroll offset、播放器 position、当前 scene id。
有了 Feed#ScrollOffsetY,第一帧开始动就能从主观描述变成可查时间点:先算 move_to_offset_change_ms,再找对应或下一帧 present,算 offset_change_to_present_ms。
用 SQL 量化输入延迟
Trace 里包含 android.input.inputevent 时,可以用 PerfettoSQL 标准库里的 android.input 模块。dispatch_latency_dur、handling_latency_dur、ack_latency_dur 依赖 inputevent 数据源;input_to_present_ms 还需要可关联的 FrameTimeline frame。缺 FrameTimeline 时仍可输出 input round trip,但 input_to_present_ms 要留空并降级。
本文不使用 Chrome EventLatency。Android App 侧以 android.input / android_input_events 和 FrameTimeline 为准;WebView/Chrome 场景另按 Chrome 模块分析。
先选择目标输入事件,避免同一进程里的 DOWN/MOVE/UP、IME、overlay 或多个窗口事件混在一起:
1 | INCLUDE PERFETTO MODULE android.input; |
目标事件选定后,再看 input round trip。重点看 dispatch、handling、ack 和 input-to-present 四列:
1 | INCLUDE PERFETTO MODULE android.input; |
字段解释如下:
dispatch_latency_dur: InputDispatcher 发出事件到 App 收到事件。read_to_dispatch_ms: InputReader 读到事件到 InputDispatcher 发出事件,能暴露分发前排队、policy/interception 或 Dispatcher 前置等待。handling_latency_dur: App 输入通道收到事件到 finish/ACK 的框架口径。它不是 click handler 专属耗时;要定位业务处理,必须再用 ViewRootImpl/InputStage slice、App marker 或 click handler section 切分。ack_latency_dur: App 发出 ACK 到系统收到 ACK。total_latency_dur: dispatch 到 ACK 完成。end_to_end_latency_dur: input read 到关联帧 present;没有关联帧时为NULL。is_speculative_frame: 帧关联是否为推测结果;为 true 时结论要降级。
end_to_end_latency_dur 为 NULL 不一定代表没有视觉反馈。它可能是事件没有关联到 FrameTimeline 帧,也可能是反馈发生在 SurfaceView、游戏引擎或另一个 layer 上。遇到这种情况,要回到 App marker、SurfaceFlinger、外部视频或领域工具,不要把 NULL 写成“没有绘制”。
排查慢输入时,先查 round-trip 慢事件,不过滤 end_to_end_latency_dur:
1 | INCLUDE PERFETTO MODULE android.input; |
只有要排序 input_to_present_ms 时,才额外过滤 end_to_end_latency_dur IS NOT NULL,并把 is_speculative_frame 写进降级状态。
如果 read_to_dispatch_ms 高,先看 InputDispatcher 状态、窗口目标、policy/interception、焦点窗口等待、IME/spy/secure window,再看 system_server 调度;不要直接从 read_to_dispatch 高跳到 CPU/freq 结论。如果 handling_ms 很高,优先回到 App 主线程看 ViewRootImpl/InputStage、click handler、Binder、锁和 IO。
如果 handling_ms 不高但 input_to_present_ms 高,优先看 post-ACK 的 App/Framework 异步路径、Activity/Fragment transaction、数据回调、下一帧生产、RenderThread、FrameTimeline、SurfaceFlinger 和调度。
调度部分也要分 Running 和 Runnable。前置条件是 sched_switch、wakeup/waking 完整,且没有 ftrace_cpu_has_data_loss、overrun/dropped 或 ftrace_setup_errors;否则只能写“残留证据中看到 Runnable/Running”,不能写“没有调度等待”。
这条查询示例覆盖 App main、RenderThread、system_server 的 InputReader/InputDispatcher、WindowManager 相关线程和 SurfaceFlinger。实际工程应由 scenario_objects 驱动 role in ('input_reader','input_dispatcher','window_manager','app_main','render_thread','surfaceflinger'),线程名只作为候选:
1 | WITH target_window AS ( |
Runnable 高说明线程想运行但没拿到 CPU,优先看其他 runnable task、RT 任务、cgroup/cpuset、uclamp/capacity、thermal、迁核和频点;Running 高说明线程已经在 CPU 上消耗时间,再去看函数级热点、Binder、锁或布局绘制。唤醒方向要再看 thread_state 的唤醒信息、IRQ context 或相邻 ftrace 事件,不能只靠 CPU 忙做结论。
常见归因路径
Read/dispatch 慢
表现通常是 read_to_dispatch_ms 或 dispatch_latency_dur 高。优先检查:
- InputReader 到 InputDispatcher 之间是否排队。
- InputDispatcher 是否忙。
- system_server 是否 Runnable 等待。
- 窗口焦点、目标窗口、InputChannel 状态是否异常。
- 是否存在 policy/interception、IME、overlay、焦点窗口等待或 WindowManager 状态切换。
- CPU 是否被其他 runnable task、RT 任务、cgroup/cpuset/uclamp、thermal 或迁核策略影响。
这类问题偏平台侧,App 只能提供页面和操作上下文。
Handling 慢
表现通常是 handling_latency_dur 高。优先检查:
- App 主线程收到 input 后是否长时间 Running。
Input阶段是否做了重计算。- click handler 是否同步发 Binder、读文件、等锁、等网络回调。
- 是否有同步布局、图片解码、复杂 Compose recomposition 或 RecyclerView 大量 bind。
这类问题大多能靠 App marker 和主线程 slice 定位。
ACK 快,但 first frame 慢
这种情况很常见:App 很快 ACK 了 input,用户仍然觉得反馈慢。优先检查:
- App 是否没有请求下一帧,或者请求发生得太晚。
- ACK 之后是否在等 Binder 回调、Activity/Fragment transaction、数据回调或窗口切换。
Animation/Traversal是否排在后一个 Vsync。- RenderThread CPU 是否卡在
syncFrameState、DrawFrame。 dequeueBuffer/queueBuffer是否说明 BufferQueue backpressure。- App GPU 完成、acquire/release fence、present fence 是否拖住这一帧。
- SurfaceFlinger/HWC/GPU composition 是否让 display frame 晚于预期。
- 主线程或 RenderThread 是否 Runnable 等待 CPU。
这里要把 input round trip 和帧生产分开看。ACK 快只说明 App 处理输入完成,不等于第一帧已经显示。
ANR 和亚 ANR
交互响应慢多数是亚 ANR 问题:用户已经感到慢,但还没到系统弹 ANR 的程度。Input ANR 主要要分两类看:
- 事件已经发给连接,App 长时间没有 ACK,通常会体现在
handling_latency_dur或 ACK 相关时间异常。 - focused event 一直等不到 focused window,通常要看 WindowManager / ActivityTaskManager、窗口创建、焦点切换和 InputDispatcher 状态。
ACK 快,但 first frame 慢 一般不会触发 input ANR,因为输入事件已经处理完;它更像首帧生产或窗口可见性问题。
滑动不跟手
滑动不跟手经常表现为第一帧位移晚、后续帧又恢复稳定。优先检查:
- 第一个有效 MOVE 到 scroll offset counter 变化的时间。
- scroll offset 变化到 present 的时间。
- MOVE 是否被合并,App 是否等到下一次 Vsync 才处理。
- 主线程是否 Runnable 等待,导致第一帧错过当前 Vsync。
- 后续 FrameTimeline 是否稳定;如果稳定,问题主要在起步 latency。
这个场景要避免只看 jank count。用户说“不跟手”,常常说的是输入到第一帧的延迟。
高速相机和 Perfetto 的边界
Perfetto 记录的是系统内部时间,用户眼睛看到的时间还包含触控采样、触控固件处理、显示扫描、面板响应和外部拍摄误差。做竞品、验收或厂商横向对比时,外部视觉测量仍然必要。
推荐组合是:
1 | 高速相机/机械手: |
外部测量给结果,Perfetto 给原因,App marker 给业务语义。三者合在一起,响应速度问题才不会停留在“体感慢”的层面。
一个最小 walkthrough 应该长这样:先定位 ACTION_UP 的 input_event_id,看到 handling_ms = 18、read_to_dispatch_ms = 3、dispatch_ms = 4;再看 ACK 后第一帧晚了 53ms,其中 App 主线程在 doFrame 前 Runnable 21ms,FrameTimeline 显示关联帧 missed 1 vsync。
这个证据串可以写成“倾向 App 主线程调度等待导致首帧晚一拍”,但如果 is_speculative_frame = true 或没有外部视频,就不能写成“确认用户可见延迟 76ms”。
一份响应速度报告可以固定成下面的格式:
1 | scenario: conversation click |
这个格式要求报告同时写起点、终点、拆分、证据和边界。证据等级固定为:确认 confirmed、倾向 likely、排除 excluded、降级 degraded、未知 unknown。后文只用英文等级。is_speculative_frame = true、缺 FrameTimeline、缺外部视频、data loss、fallback 路径都会降低等级。响应速度问题不能只给一个总耗时;总耗时说明用户慢在哪里感知到,拆分才说明工程上该找谁。没有外部测量时,报告只能写系统内部延迟,不能写用户可见绝对延迟。
收束
交互首帧响应的分析顺序可以固定下来:
- 定义起点:DOWN、UP、有效 MOVE、业务 click。
- 定义终点:按压态第一帧、转场第一帧、内容第一次位移、首屏内容完成。
- 用
android_input_events拆read_to_dispatch、dispatch、handling、ack、input_to_present_ms。 - 用 App marker 说明业务状态,用关联帧 present 作为屏幕终点。
- 用 FrameTimeline、RenderThread、SurfaceFlinger、CPU 调度解释第一帧为什么早或晚。
- 用高速相机校准用户可见延迟。
启动速度看的是一个长场景,流畅度看的是连续帧稳定性,交互首帧响应看的是用户动作后的第一次反馈。三者分开,Perfetto 里的证据才会清楚。
参考文档
- PerfettoSQL standard library - android.input
- TraceConfig reference - AndroidInputEventConfig
- Android Jank detection with FrameTimeline
- ATrace: Android system and app trace events
- Track events
- Trace configuration
- CPU Scheduling events
- Buffers and dataflow
- Capture traces with adb commands - Input
- Android
<profileable>manifest element - ANR detection in InputDispatcher
- ViewRootImpl input pipeline
关于我 && 博客
欢迎关注 Android Performance。