0Pricing
Android Academy · 课时

衡量性能

了解性能分析器、跟踪记录和基准测试

衡量性能 是 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 衡量性能
  2. 驾驭重组
  3. 内存泄漏与修复
  4. 启动与基线配置文件
← 返回 Android Academy