衡量性能
了解性能分析器、跟踪记录和基准测试
衡量性能 是 CoddyKit 上的免费 Android Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。
先测量,再优化
性能工作的黄金法则是:先测量,绝不猜测。人们凭直觉判断哪里慢,几乎总是会出错。
在本课中,您将学习 Android 提供的、用于观察性能的工具:性能分析器、系统跟踪和微基准测试。能够进行测量后,每次优化都会成为数据驱动的决策,而不再只是凭感觉判断。
- 性能分析器会实时显示 CPU、内存和能耗。
- 系统跟踪会准确揭示每一帧的时间花在了哪里。
- 基准测试会提供可重复的数值,便于您在不同构建之间进行比较。
16 毫秒帧预算
在 60Hz 屏幕上,系统每 16.67ms 绘制一帧。如果您的应用无法在这个时间窗口内准备好一帧,该帧就会被丢弃,用户会感受到卡顿(不流畅)。在 120Hz 设备上,预算会缩短到约 8ms。
性能优化的核心,就是始终控制在这个预算之内。下面的注释展示了您需要牢记的计算方式。
// Frame budget math
// 60 Hz -> 1000ms / 60 = 16.67ms per frame
// 90 Hz -> 1000ms / 90 = 11.11ms per frame
// 120 Hz -> 1000ms / 120 = 8.33ms per frame
//
// Exceed the budget on the UI thread = a dropped frame = visible jank.
fun frameBudgetMs(refreshHz: Int): Double = 1000.0 / refreshHz
fun main() {
println("60Hz -> %.2f ms".format(frameBudgetMs(60)))
println("120Hz -> %.2f ms".format(frameBudgetMs(120)))
}Android Studio 性能分析器
Android Studio 性能分析器(视图 > 工具窗口 > 性能分析器)可以连接到正在运行的应用,并实时显示 CPU、内存、能耗和网络的时间线。
- CPU:记录方法跟踪和系统跟踪,以找出最耗时的代码路径。
- 内存:观察内存分配并捕获堆转储,以查找泄漏。
- 能耗:发现唤醒锁和过多的任务,避免它们耗尽电量。
请始终在真实设备上分析接近发布配置的构建版本。调试构建版本和模拟器会给出误导性的数值,因为优化功能处于关闭状态。
真实场景的宏基准测试
Jetpack 宏基准测试库会在真实设备上测量完整的用户操作流程(启动、滚动、导航),并报告帧耗时和启动耗时等稳定指标。
您需要编写一个测试来启动应用并执行一段操作流程;该库会多次运行这段流程,并提供中位数和第 99 百分位数。
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule val rule = MacrobenchmarkRule()
@Test
fun coldStartup() = rule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD
) {
pressHome()
startActivityAndWait()
}
}热点代码的微基准测试
如果您需要了解单个函数的执行速度,请使用微基准测试库。它会在紧凑循环中运行您的代码,预热 JIT,并排除噪声后报告每次操作所需的纳秒数。
它适用于解析器、序列化器、排序操作,或您频繁调用的任何受 CPU 限制的辅助函数。
@RunWith(AndroidJUnit4::class)
class JsonParseBenchmark {
@get:Rule val benchmarkRule = BenchmarkRule()
@Test
fun parseLargePayload() {
val raw = loadSampleJson()
benchmarkRule.measureRepeated {
val parsed = parseUsers(raw) // code under test
// assertEquals avoids the compiler dropping the result
assertTrue(parsed.isNotEmpty())
}
}
}使用 Perfetto 进行系统跟踪
系统跟踪会逐帧记录每个线程和系统执行的操作。Android Studio 的 CPU 性能分析器可以记录系统跟踪,您也可以使用 adb 捕获跟踪,然后在 Perfetto(ui.perfetto.dev)中打开。
跟踪会显示令人头疼的红色帧、主线程上的工作,以及 GPU 花费的时间。它是诊断卡顿的最佳工具。
# Record a 5-second system trace from the command line
adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace \
-t 5s sched freq idle am wm gfx view
# Pull it to your machine, then open in https://ui.perfetto.dev
adb pull /data/misc/perfetto-traces/trace.perfetto-trace自定义跟踪区段
跟踪默认会显示框架执行的工作。要标记您自己的代码,请将其包装在一个命名的跟踪区段中。这样,这些名称就会以带标签的区块形式出现在 Perfetto 中,让您可以轻松发现热点。
请使用 androidx.tracing API,这样这些区段就会同时出现在调试跟踪和基准测试跟踪中。
import androidx.tracing.trace
fun loadDashboard(repo: Repo): Dashboard {
return trace("loadDashboard") {
val user = trace("fetchUser") { repo.user() }
val feed = trace("fetchFeed") { repo.feed() }
Dashboard(user, feed)
}
}
// In Perfetto you will now see named slices:
// loadDashboard > fetchUser, fetchFeed运行时跟踪丢失的帧
您不可能始终连接着性能分析器。JankStats 是一个 Jetpack 库,它可以从正在运行的应用内部报告卡顿帧,因此您可以记录这些帧,或将汇总数据发送到分析系统。
它会接入窗口,并在某一帧超过预算时调用您的回调。
val jankStats = JankStats.createAndTrack(window) { frameData ->
if (frameData.isJank) {
Log.w("Jank", "Janky frame: ${frameData.frameDurationUiNanos} ns")
analytics.logJank(frameData.frameDurationUiNanos)
}
}
// Pause/resume with your screen lifecycle
override fun onResume() { super.onResume(); jankStats.isTrackingEnabled = true }
override fun onPause() { super.onPause(); jankStats.isTrackingEnabled = false }解读数据:百分位数
单个平均值会掩盖问题。如果平均帧耗时为 10ms,但第 99 百分位数为 40ms,就意味着 1% 的帧存在卡顿,用户会明显感受到。请始终查看 P50、P90、P99,而不只是平均值。
基准测试会为您报告这些数据。下面的辅助代码展示了如何对原始帧样本进行处理。
fun percentile(samplesMs: List<Double>, p: Int): Double {
val sorted = samplesMs.sorted()
val index = ((p / 100.0) * (sorted.size - 1)).toInt()
return sorted[index]
}
fun main() {
val frames = listOf(8.0, 9.0, 10.0, 9.5, 11.0, 40.0, 9.0, 10.5)
println("P50 = %.1f ms".format(percentile(frames, 50)))
println("P99 = %.1f ms".format(percentile(frames, 99)))
}可重复的测量流程
可信的数据来自严谨的流程。每次都请按照以下清单操作:
- 使用发布版 / 不可调试构建版本(启用 R8)。
- 在实体设备上运行,连接电源,并确保设备处于稳定的温度状态。
- 将场景重复运行数次,并报告中位数。
- 每次只更改一项内容,然后重新测量。
- 与保存的基线进行比较,以证明性能确实有所改善。
如果跳过这些步骤,您追逐的就会是噪声,而不是真正的问题。
时间究竟花在了哪里
大多数 Android 卡顿都来自少数几类原因。了解这些原因,您就知道该把工具用在哪里:
- 主线程工作:在界面线程上执行磁盘、网络或 JSON 操作。
- 过度重组:Compose 重绘了过多内容。
- 频繁分配:垃圾回收造成暂停。
- 布局遍历:布局嵌套过深,或被反复测量。
本课程接下来的课程将分别直接处理重组、内存泄漏和启动问题。测量结果会告诉您应该先修复哪一个问题。
快速检查
您希望在真实设备上多次运行应用,稳定且可重复地测量应用冷启动所需的时间。哪种工具最适合?
回顾:现在您可以进行测量了
您已经学会在进行任何更改之前,让性能变得可见:
- 16ms 帧预算决定了画面是流畅还是卡顿。
- Android Studio 性能分析器实时显示 CPU、内存和能耗情况。
- 宏基准测试和微基准测试提供可重复的数据。
- Perfetto 跟踪和自定义
trace()区段可以揭示时间花在了哪里。 - JankStats从正在运行的应用内部报告卡顿。
- 查看P50/P90/P99,在真实设备上分析发布版构建,并且每次只更改一项内容。
接下来,我们将把这些方法应用到 Compose 卡顿的最大来源之一:重组。
常见问题解答
「衡量性能」课时是免费的吗?
是的 — 「衡量性能」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。
「衡量性能」这节课中我会学到什么?
了解性能分析器、跟踪记录和基准测试 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Android Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「衡量性能」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Android Academy 课中编写并运行代码吗?
能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。