EN

Android Systrace 响应速度实战 3 :响应速度延伸知识

字数统计: 5.6k阅读时长: 21 min
2021/09/13
loading

在讨论 Android 性能问题的时候,卡顿、响应速度、ANR 常被放在一起。它们可能有共同原因,也可能同时出现,但不是按严重程度划分的三个等级。用户说“卡”,还需要分清是动画不连贯、操作后等得太久,还是系统触发了无响应判定。

动画或列表滚动的不连贯通常称为 Jank;应用启动、亮灭屏或场景切换耗时过长属于 响应慢(slow response / high latency);ANR 则由具体的系统超时条件触发。掉帧率、启动耗时和 ANR 率各有测量口径,分析时要分开。

本文是响应速度系列的第三篇,主要是讲在使用 Systrace 分析应用响应速度问题的时候,其中的一些延伸知识,包括启动速度测试、Log 输出解读、Systrace 状态解读、三方启动库等内容

版本与历史说明(2026-10-14 核查):保留 2021 年的分析图片和章节。Traceview 已弃用,下文改用 Android Studio CPU Profiler 的术语,并修正采样单位。线程/组件示例按 AOSP Android 10/11 解释,命令单位另核对 Android 16;App Startup 补充 1.2.0 的依赖和按需初始化行为。不同工具截图不是同一次实验,旧图中的状态、组件名和“加载完成”标注以正文修正为准。

本文目录

Systrace 系列文章如下

  1. Systrace 简介
  2. Systrace 基础知识 - Systrace 预备知识
  3. Systrace 基础知识 - Why 60 fps ?
  4. Systrace 基础知识 - SystemServer 解读
  5. Systrace 基础知识 - SurfaceFlinger 解读
  6. Systrace 基础知识 - Input 解读
  7. Systrace 基础知识 - Vsync 解读
  8. Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解
  9. Systrace 基础知识 - MainThread 和 RenderThread 解读
  10. Systrace 基础知识 - Binder 和锁竞争解读
  11. Systrace 基础知识 - Triple Buffer 解读
  12. Systrace 基础知识 - CPU Info 解读
  13. Systrace 流畅性实战 1 :了解卡顿原理
  14. Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析
  15. Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问
  16. Systrace 响应速度实战 1 :了解响应速度原理
  17. Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例
  18. Systrace 响应速度实战 3 :响应速度延伸知识
  19. Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇
  20. Systrace 线程 CPU 运行状态分析技巧 - Running 篇
  21. Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇

1. Systrace 中进程三种状态解读

这里观察的是 线程调度状态,不是整个进程只有一个状态。同一进程的主线程可以等待,而工作线程仍在运行。旧 Systrace 常把 Running 显示为绿色、Runnable 为蓝色、Sleep 留白;颜色随查看器变化,函数色块更不能当作调度状态。

选中线程状态轨道确认标签,再看对应调度事件。除了这三种,还可能遇到不可中断等待等状态;空白或缺失数据不能一律解释为 Sleep。

1.1 如何分析 Sleep 状态的 Task

Sleep 表示线程此时未在运行、也未在等待 CPU 调度,不能仅凭状态区分“主动/被动”或判断是否正常。常见等待包括 Looper 的 poll、同步 Binder 回复、锁/条件变量和显式 sleep。nativePollOnce 在没有到期消息时等待通常合理,但启动关键任务为何还没送达、消息是否延迟或被屏障阻挡,仍可能值得查。

下图包含 IWindowSession.remove 的 Binder 调用。先把调用区间与线程状态重叠部分对应起来;同步调用可能等待远端,oneway 语义则不同。调用次数多并不独立证明瓶颈,要看关键路径上的累计等待和对端工作。

QBslSMaNhoheCWfY

点击 binder transaction 可以跟踪请求和回复:

FqqYKpVnNGUuivrq

图中事务墙钟时长为 34.216 ms,不能全算 Sleep 或远端 CPU 时间。该图 outgoing 指向 29928→1515,请求的 incoming 回流为 1515→29928;原图底部把 outgoing 误写成了 incoming。仍需结合事务关联与接收线程,不能把任意 incoming flow 都认定为回复。

唤醒信息可帮助继续追踪依赖:

zPFoHiQJvTWMmUuF

YJH0UWMlVDuJjSro

这里显示 wakeup from tid: 1515。它指出本次唤醒来源,不直接证明锁的持有者、完整等待对象或根因;还要检查等待前的栈、对端区间和后续调度。方法名 invokeSuspend 也不等于该 OS 线程一直在 Sleep。

1.2 如何分析 Running 状态的 Task

Running 表示线程被调度到某个 CPU 上。区间变长时,先比较工作量与输入,再看核心能力、频率、缓存/内存访问和调度切换。函数墙钟区间可能包含多个 Running 和等待区间,不能与实际 CPU 时间混用。

NDvF1X4GFiUpC6vN

K49yBsgPUrHkYyw6

图中选中的是 CPU 0 上的运行区间,另一图的 5.112 ms 是选区。截图没有给出 CPU 拓扑与频率数值,不能据此判断“小核跑错了”或频率不足。前台 UI/RenderThread 也不保证始终在大核运行;调度要平衡延迟、能耗、温控、亲和性和可用算力。

1.3 如何分析 Runnable 状态的 Task

从睡眠被唤醒的线程先具备运行条件,再被调度执行;Running 线程被抢占后也可能回到 Runnable。Runnable 不保证立即运行,其区间很短时甚至不容易在当前缩放下分辨。

3gilFNQA8lh1A7l0

这张旧图主要是 Java 线程状态示意,不能直接当成 Linux 调度状态机。Java Thread.State 的 RUNNABLE 可以包含等待 CPU 的线程;RUNNING、READY 不是该枚举的独立值。VM 的 BLOCKED 与内核不可中断等待也不是同一状态;Object.notify 唤醒等待者后仍需重新竞争 monitor,不能理解为立刻进入 Running。

GR7crsOwDOy4X8qL

分析长 Runnable 时,查看允许在哪些 CPU 运行、优先级、cpuset/affinity、同核竞争和可用容量。全系统繁忙是候选原因之一,但只剩某些核心忙、亲和性限制或更高优先级任务占用也会造成等待,不能据此断言所有 CPU 已跑满。

2. TraceView 工具在响应速度方面的使用

旧标题沿用 TraceView,但截图是 Android Studio CPU Profiler。独立 Traceview 已弃用,官方建议 Android Studio 3.2 及以后使用 CPU Profiler 检查方法 trace。方法采样/插桩、系统 Trace 和工具的查看界面要分开理解。

image-20211028011509615

2.1 如何抓取应用启动时候的 TraceView

这是 ART Java/Kotlin 方法采样的诊断示例,不是通用的 native 栈或端到端启动基准。先核对设备 am start --help、目标应用的调试权限和可写输出路径。--sampling 的单位是 微秒:1000 微秒才是 1 ms,原文的 --sampling 1 不是每毫秒采一次。单位见 AOSP Android 16 命令帮助。

1
2
3
4
5
6
7
# Example package/component: replace both with your installed debug target.
# -S force-stops the target before launch; --sampling is in microseconds.
adb shell am start -S -n com.aboback.wanandroidjetpack/.splash.SplashActivity \
--start-profiler /data/local/tmp/startup-methods.trace --sampling 1000
# Stop after the agreed startup scenario has completed.
adb shell am profile stop com.aboback.wanandroidjetpack
adb pull /data/local/tmp/startup-methods.trace .

-S 会停止目标包,避免复用已有进程;它不清空文件缓存或应用数据,也不模拟桌面触摸链路。启动失败、权限拒绝或 Activity 复用时先处理原因,不能把空 trace 当作启动很快。按实际业务终点停止采集,而不是固定等待 10 秒就认定完全加载。文件导入方式与格式兼容性按所用 Studio 版本确认。

2.2 TraceView 工具怎么看

导入方法 trace 后,先确认进程、线程、采集区间、采样或插桩模式及计时口径。下面旧图中,绿色区域还包括应用打包的第三方库,并不只代表作者自己的代码;颜色应结合方法全名和工具图例解释。

阅读 Top Down/Bottom Up 时,区分包含子调用的总耗时与自身耗时;父子项不能重复相加。采样可能漏掉短调用,时间线和调用次数也不能当作完整逐次执行日志。

Application.onCreate

vRpdMHl8CTzCGcxS

Activity.onCreate

Jl9s7vZbMQTTrWIT

doFrame

X4f8vX27JS5x4TyF

WebView 初始化

jxmFl0K9M8cpNCWy

这张 WebView 图包含腾讯 TBS/X5 的类名,属于该样例的第三方实现路径,不能推广为所有系统 WebView 的初始化栈。

2.3 TraceView 工具的弊端

方法采样有统计误差,方法插桩会记录更多边界,但往往有更明显的运行开销;不能把两种模式混称为“TraceView 高频采样”。更短的请求间隔也不保证每次按时采到,尤其不应按 1 微秒请求去推算实际频率。

这些数据可以帮助找热点和调用关系,也能在注明计时口径、样本量和开销的前提下比较。最终启动耗时要用较低开销的系统 Trace/启动基准复测;不要用被 profiling 扰动的单次方法时长冒充用户实际延迟。

3. SimplePerf 工具在启动速度分析的使用

simpleperf 可以采样 Java/Kotlin 与 native 调用栈,但覆盖取决于 Android 版本、权限、符号和展开信息。官方文档说明 Android 9 起支持解释执行、JIT 与 AOT 的 Java 代码;Android 10 起可按文档配置 profileable release 应用。它不是无条件给任意生产 APK 抓全栈的工具。

从所安装的 NDK/simpleperf 脚本目录执行,并核对该版本 --help。下面按官方应用分析指南显式指定目标 Activity 与采集参数:

1
2
3
python3 app_profiler.py -p com.aboback.wanandroidjetpack -a .splash.SplashActivity \
-r "-e task-clock:u -f 1000 -g --duration 10"
python3 report_html.py

当前脚本的 -a 路径会先尝试停止已有进程,再启动采集与目标 Activity;保留脚本/NDK 版本和日志,确认实际是冷启动。若只传 -p,则需预先保证目标不在运行,等采集器准备好后手动启动;结束的是采集,不是要求杀掉应用。进程发现与附着存在时序,不能假设覆盖到了最早每一条指令。下面保留原来的历史日志,它明确带有 10 秒采集期限:

1
2
3
4
5
$ python app_profiler.py -p com.aboback.wanandroidjetpack
INFO:root:prepare profiling
INFO:root:start profiling1
INFO:root:run adb cmd: ['adb', 'shell', '/data/local/tmp/simpleperf', 'record', '-o', '/data/local/tmp/perf.data', '-e task-clock:u -f 1000 -g --duration 10', '--log', 'info', '--app', 'com.aboback.wanandroidjetpack'] simpleperf I environment.cpp:601] Waiting for process of app com.aboback.wanandroidjetpack
simpleperf I environment.cpp:593] Got process 32112 for package com.aboback.wanandroidjetpack

采集成功后,report_html.py 从 perf.data 生成报告;保存匹配构建的未剥离 native 库、binary_cache 和需要的混淆映射,避免把 unknown 栈当成没有耗时。

itcLtktb5xxv5gp0

这类火焰图宽度表示所选事件的采样权重,横向不是启动时序。示例 task-clock:u 关注用户态 on-CPU 工作,不能靠它直接解释 Binder/锁等待或内核耗时;若需要 off-CPU 分析,先确认设备支持相关功能,或与系统 Trace 联合分析。缺样本、丢样本和符号缺失分别记录。

更详细的工具说明:

  1. simpleperf README
  2. Android application profiling
  3. Android platform profiling
  4. Executable commands reference
  5. Scripts reference

4. 其他组件启动时在 Systrace 中的位置

4.1 Service 的启动

以下是 AOSP android-11.0.0_r1 ActivityThread 的节选,省略调试日志;是框架源码,不是直接放进应用的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
sendMessage(H.CREATE_SERVICE, s);
}

public final void scheduleBindService(IBinder token, Intent intent,
boolean rebind, int processState) {
updateProcessState(processState, false);
BindServiceData s = new BindServiceData();
s.token = token;
s.intent = intent;
s.rebind = rebind;
sendMessage(H.BIND_SERVICE, s);
}

系统通过 ApplicationThread 调度创建/绑定 Service,回调再由 H 投递到应用主线程。startService 返回不等于 Service.onCreate 已执行;还可能有系统调度或目标进程创建。MessageQueue 按到期时间、屏障和同步/异步属性取消息,不是简单的提交先后顺序。

NRY3YuU1Bsgk6wvv

本图里后续回调出现在一个 doFrame 之后,这是样本中的次序,不保证 Service 总在首帧之后执行,更不证明该帧已经物理呈现。图内“注册广播接收器”也应区分:broadcastReceiveComp 表示组件接收广播的回调区间,注册是另一条调用路径。

r463HjnnO0KgdOFl

4.2 执行自己的 Message

这里是 Handler 回调区间,图中标签带有 AndroidX LiveData 类名。应结合 Handler/Runnable 名称和内部调用确认业务,不能把所有此类 Message 都直接算成自己的业务代码。

iUZhyqUStVIyZHtf

4.3 启动 Service

这张图的标签是 LoadedApk$ServiceDispatcher$RunConnection,属于客户端服务连接状态分发,可能调用 ServiceConnection.onServiceConnected 等回调,不是 Service.onCreate 本身。可对照 Android 11 LoadedApk;分析服务启动时还需找服务所在进程的 create/bind/args 区间。

8RzVxF9ai3r420W0

4.4 启动 BroadcastReceiver

图中 broadcastReceiveComp 是清单组件接收广播的执行区间,不能只凭 Handler 的数字编号跨版本识别事件。

kY3rkAhlqk7klnOL

下图是在 Activity.onStart 中调用注册接口的过程;注册调用在哪个线程执行,与以后 onReceive 的回调线程不是一个问题。动态接收器默认回调主线程,指定 scheduler Handler 的重载可改变派发线程,并不会自动沿用注册时的线程。注册位置还应与所需生命周期及注销配对。

UR245WLGUvPEcvTz

4.5 ContentProvider 的启动时机

正常 Android 11 进程绑定路径中,Application 创建/attach 之后、Application.onCreate 之前安装本进程需要初始化的 Provider。不是在每次 Activity 启动时运行整个 APK 的所有 Provider,也不是每次 query 都重跑 onCreate。图中的 publishContentProviders 是向系统发布已安装 Provider 的阶段,不能把这一个 Binder 区间当作全部 Provider 初始化耗时。

6PdzLLkZ7hdA61Tk

019y0cn5kNNGFVnm

5. AppStartup 是否能优化启动速度?

三方库的初始化

有些库,你在 Application.onCreate 里明明没调用初始化,它却已经悄悄做了。常见的办法就是通过清单中的 ContentProvider 自动初始化。它依赖 Provider 的进程归属、启用状态和系统绑定流程;不是启动时把整个 APK 声明的所有 Provider 都执行一遍。

SDK 的初始化方式随版本变化。例如 WorkManager 2.6 起使用 App Startup,旧版本独立 Provider 的移除方法不能直接套到新版本。应检查当前依赖和最终合并清单,而不是凭 SDK 名称推断。

VhpUlBTz1UNteVQz

这张图概括正常进程初始化中的先后关系。多个库分别自动初始化会增加启动路径工作,也让依赖关系不容易看清;但开发者并非绝对无法控制,是否能关闭自动初始化、替换入口或延迟执行,要看每个 SDK 的公开约定。

AppStartup 库

App Startup 让采用 Initializer 接口的组件共享 InitializationProvider,并通过 dependencies() 声明前置依赖。它不会自动改造任意 SDK 的 Provider,也不承诺给互不依赖的初始化器固定全局顺序。App Startup 1.2.0 的 AppInitializer 会递归初始化依赖、缓存结果并检测循环;自动初始化通常在主线程,手动调用也不会自动切到后台。

原文的“50+ Provider 才有明显收益”和“二三十个库只减少 20 ms 以内”缺少可复核的设备、版本、基线和重复样本,不保留为通用阈值。收益取决于实际移除/延后的关键路径工作;仅合并入口不会消除 SDK 自身的初始化开销。

若要延后一个已有 Initializer,在应用清单的 <application> 内移除它的自动发现项;清单根元素需声明 xmlns:tools="http://schemas.android.com/tools":

1
2
3
4
5
6
7
8
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.ExampleLoggerInitializer"
tools:node="remove" />
</provider>

这只移除 ExampleLoggerInitializer 的 metadata。如果另一个自动初始化器把它列为依赖,它仍可能在启动时执行。确认整个依赖图后,在首次使用之前按该 SDK 要求的线程调用:

1
2
AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer.class);

ExampleLoggerInitializer 是示例名,需替换为项目已有、实现 Initializer 的类,并导入 androidx.startup.AppInitializer。不要只移除清单项却漏掉初始化,也不要为了延迟一个库而删除所有库共享的 Provider。异步工作完成条件和多进程初始化范围仍要由应用/SDK管理。

总结

  1. 用统一入口和显式依赖管理参与的组件,减少重复 Provider 入口。
  2. 将非首屏必需的初始化移出启动关键路径,代价可能转移到首次使用;同时观察 TTID、TTFD 和首次操作延迟。
  3. 控制顺序不等于减少工作或后台化,是否有效需要相同条件下的重复测量。

需要 App 开发同学验证

  1. 检查目标构建的 Merged Manifest/最终 APK,确认 Provider、进程和 Initializer metadata,而不只数源清单条目。
  2. 记录各初始化项的线程、墙钟/CPU时间及依赖,找出关键路径。
  3. 对照 SDK 的初始化合同,确认哪些可延迟、由谁触发、首次使用前如何保证就绪。
  4. 修改后验证冷启动、后台入口、多进程及首次使用,保留失败/缺失样本;用相同编译和数据条件比较结果。

6. IdleHandler 在 App 启动场景下的使用

IdleHandler 在所属 MessageQueue 的 Looper 线程上执行。在 AOSP Android 10 MessageQueue.next 中,队列为空或队头消息尚未到期时可以运行 idle handlers;不要求未来所有消息都已执行完。已到期的同步屏障还会影响这个判断。

XaSQsq3pfgDgCNVe

原图“所有 Message 都执行完成后”的注释过强。主线程暂时空闲时,网络、后台任务、GPU 或下一次绘制仍可能没有完成;队列持续繁忙时,idle handler 也可能迟迟不执行。

  1. 延后轻量、非必需的工作。 在 Activity.onCreate 注册主队列 IdleHandler,不保证它一定在首帧之后运行,也不会自动使用后台线程。耗时回调会阻挡随后到达的输入/帧任务。queueIdle() 返回 false 会移除,返回 true 会保留以等待以后的空闲机会;不要把 true 当成独立循环或保证执行的定时器。生命周期结束时移除不再需要的处理器。

7fLroBfAyXPaUu8W

  1. 完整显示指标应由业务就绪条件触发。 Activity.reportFullyDrawn() 从 API 19 提供,用于报告关键内容/可用状态;它不要求通过 IdleHandler 调用。下图是旧示例:先收到数据完成信号,再等一次主队列空闲。数据已返回和队列空闲仍不足以证明列表、图片都已经展示。

BN97DrcXfHiqA5DY

图中的 Android M(API 23)判断与使用 Looper.getQueue() 有关,不能解释为 reportFullyDrawn 到 API 23 才提供。重做时明确成功、空数据和错误页各自的就绪条件,防止重复注册、Activity 已销毁或旧请求回调误报。使用 AndroidX 的就绪报告工具时,也要保留同样的业务条件。

1xrN3YMzAY71ZAWi

总览图中的 onWindowFocusChange 应为 onWindowFocusChanged,窗口获焦不等于初始显示或内容就绪。am start -W 的等待结果与焦点回调也不是相同指标;Splash→Main 的统计范围取决于实际启动序列与系统实现。对比 TTID/TTFD 官方说明,同时保留首次显示和完整显示的定义。

A5mH2AW4U06l43jk

这个历史输出包含 SplashActivity 的 Displayed +968 ms、MainActivity 的 Displayed +475 ms 和 Fully drawn +3s221ms;am start -W 显示 TotalTime 968、WaitTime 975。这些字段的起点与 Activity 归属不同,不能相加或直接相减求某一阶段,也不能把 +3s221ms 自动当作从触摸桌面图标开始的完整耗时。截图命令还用 sleep 5 后读取现有 logcat 缓冲,可能混入旧记录;实际测量要按时间、包名/Activity及本次启动关联日志。

正确上报 TTFD 能补充首帧指标,并不使原来 TTID 变成“错误时间”。漏报、数据失败或采集不全要单独记录,不记作 0。

系列文章

  1. Systrace 响应速度实战 1 :了解响应速度原理
  2. Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例
  3. Systrace 响应速度实战 3 :响应速度延伸知识
  4. Systrace 基础知识系列-放个链接在这里方便大家直接点过去

参考文章

  1. Android 应用启动全流程分析
  2. 探究 | App Startup 真的能减少启动耗时吗
  3. Jetpack 新成员,App Startup 一篇就懂
  4. App Startup
  5. Android App 启动优化全记录
  6. Android application profiling

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. 本文目录
  • 1. Systrace 中进程三种状态解读
    1. 1. 1.1 如何分析 Sleep 状态的 Task
    2. 2. 1.2 如何分析 Running 状态的 Task
    3. 3. 1.3 如何分析 Runnable 状态的 Task
  • 2. TraceView 工具在响应速度方面的使用
    1. 1. 2.1 如何抓取应用启动时候的 TraceView
    2. 2. 2.2 TraceView 工具怎么看
    3. 3. 2.3 TraceView 工具的弊端
  • 3. SimplePerf 工具在启动速度分析的使用
  • 4. 其他组件启动时在 Systrace 中的位置
    1. 1. 4.1 Service 的启动
    2. 2. 4.2 执行自己的 Message
    3. 3. 4.3 启动 Service
    4. 4. 4.4 启动 BroadcastReceiver
    5. 5. 4.5 ContentProvider 的启动时机
  • 5. AppStartup 是否能优化启动速度?
    1. 0.0.1. 三方库的初始化
    2. 0.0.2. AppStartup 库
  • 0.1. 总结
    1. 0.1.1. 需要 App 开发同学验证
  • 6. IdleHandler 在 App 启动场景下的使用
  • 系列文章
  • 参考文章
  • 关于我 && 博客