由应用驱动的性能分析

在 Android 15(API 级别 35)及更高版本中,应用驱动的性能分析功能可让您的应用使用 ProfilingManager API 或 AndroidX Core(androidx.core:core 1.15.0 或更高版本)构建器在代码中明确启动和停止受支持的性能分析记录。如果您想控制性能分析时间或会话参数(例如时长、缓冲区政策和采样率),或者想针对特定的用户体验历程或运行时条件,请使用直接性能分析。

添加依赖项

为了在使用 ProfilingManager API 时获得最佳体验,请将以下 Jetpack 库添加到 build.gradle.kts 文件中。

Kotlin

   dependencies {
       implementation("androidx.tracing:tracing-ktx:2.0.5")
       implementation("androidx.core:core:1.19.1")
   }
   

Groovy

   dependencies {
       implementation 'androidx.tracing:tracing-ktx:2.0.5'
       implementation 'androidx.core:core:1.19.1'
   }
   

录制系统跟踪记录

添加所需的依赖项后,使用以下代码记录系统跟踪。此示例展示了如何启动性能分析会话,同时在主线程之外安全地管理繁重的操作。

Kotlin

@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@Composable
fun ProfiledScreen(modifier: Modifier = Modifier) {
    // Use the application context: requestProfiling resolves the ProfilingManager
    // system service from it, so there's no reason to hand it a short-lived Activity.
    val appContext = LocalContext.current.applicationContext
    val scope = rememberCoroutineScope()

    Button(
        onClick = {
            // Run the orchestration off the main thread. Profiling a heavy operation
            // on the UI thread would freeze the UI (ANR) and distort the very metrics
            // you're trying to capture.
            //
            // Note: this scope is tied to composition. If the user leaves this screen
            // mid-session, the coroutine is cancelled and stopSignal.cancel() might not
            // run, but setDurationMs() acts as a safety net and ends the profile.
            scope.launch(Dispatchers.Default) {
                val callbackExecutor = Dispatchers.IO.asExecutor()
                val resultCallback = Consumer<ProfilingResult> { profilingResult ->
                    if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
                        Log.d("ProfileTest", "Result file: ${profilingResult.resultFilePath}")
                    } else {
                        // errorMessage explains the failure (e.g., rate limiting); keep it.
                        Log.e(
                            "ProfileTest",
                            "Profiling failed errorCode=${profilingResult.errorCode} " +
                                "errorMessage=${profilingResult.errorMessage}"
                        )
                    }
                }

                val stopSignal = CancellationSignal()
                val requestBuilder = SystemTraceRequestBuilder().apply {
                    setCancellationSignal(stopSignal)
                    setTag("FOO") // Caller-supplied tag for identification.
                    setDurationMs(60000) // Hard cap: ends the session if cancel() never fires.
                    setBufferFillPolicy(BufferFillPolicy.RING_BUFFER)
                    setBufferSizeKb(32768)
                }

                // 1. Start the session. This is asynchronous system IPC. The tracing
                //    engine takes a moment to start and allocate buffers.
                requestProfiling(appContext, requestBuilder.build(), callbackExecutor, resultCallback)

                // 2. The API exposes no "profiling started" signal, so pad with a short,
                //    best-effort delay before running the code you care about. This is
                //    approximate. Increase it on slower or heavily loaded devices.
                delay(STARTUP_PADDING_MS)

                // 3. The session is already recording every thread in your app. This slice
                //    doesn't scope what's captured. It just labels this region of the
                //    timeline so heavyOperation() is easier to find. trace { } closes the
                //    section even if the block throws.

                trace("MyApp:HeavyOperation") {
                    heavyOperation()
                }

                // 4. Stop recording. Until this fires or the setDurationMs() cap is
                //    reached (whichever comes first), the session keeps capturing app-wide
                //    activity.

                stopSignal.cancel()
            }
        }
    ) {
        Text("Run & Profile Heavy Operation")
    }
}

// Best-effort wait for the system trace engine to initialize before profiling.
// There is no deterministic start callback; tune this for your target devices.
private const val STARTUP_PADDING_MS = 100L

fun heavyOperation() {
    // Background computations to profile.
}

Java

void sampleRecordSystemTrace(Context context) {
  // Use the application context: requestProfiling resolves the ProfilingManager
  // system service from it, so there's no reason to hand it a short-lived Activity.
  Context appContext = context.getApplicationContext();

  // Run the orchestration off the main thread. Profiling a heavy operation
  // on the UI thread would freeze the UI (ANR) and distort the very metrics
  // you're trying to capture.
  profilingExecutor.execute(() -> {
    Executor callbackExecutor = Executors.newSingleThreadExecutor();
    Consumer<ProfilingResult> resultCallback = profilingResult -> {
      if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
        Log.d("ProfileTest", "Result file: " + profilingResult.getResultFilePath());
      } else {
        // errorMessage explains the failure (e.g., rate limiting); keep it.
        Log.e(
            "ProfileTest",
            "Profiling failed errorCode=" + profilingResult.getErrorCode()
                + " errorMessage=" + profilingResult.getErrorMessage());
      }
    };

    CancellationSignal stopSignal = new CancellationSignal();
    SystemTraceRequestBuilder requestBuilder = new SystemTraceRequestBuilder();
    requestBuilder.setCancellationSignal(stopSignal);
    requestBuilder.setTag("FOO"); // Caller-supplied tag for identification.
    requestBuilder.setDurationMs(60000); // Hard cap: ends the session if cancel() never fires.
    requestBuilder.setBufferFillPolicy(BufferFillPolicy.RING_BUFFER);
    requestBuilder.setBufferSizeKb(32768);

    // 1. Start the session. This is asynchronous system IPC. The tracing
    //    engine takes a moment to start and allocate buffers.
    Profiling.requestProfiling(
        appContext, requestBuilder.build(), callbackExecutor, resultCallback);

    // 2. The API exposes no "profiling started" signal, so pad with a short,
    //    best-effort delay before running the code you care about. This is
    //    approximate. Increase it on slower or heavily loaded devices.
    try {
      Thread.sleep(STARTUP_PADDING_MS);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      stopSignal.cancel();
      return;
    }

    // 3. The session is already recording every thread in your app. This slice
    //    doesn't scope what's captured. It just labels this region of the
    //    timeline so heavyOperation() is easier to find. try/finally closes the
    //    section even if heavyOperation() throws.
    Trace.beginSection("MyApp:HeavyOperation");
    try {
      heavyOperation();
    } finally {
      Trace.endSection();
    }

    // 4. Stop recording. Until this fires or the setDurationMs() cap is
    //    reached (whichever comes first), the session keeps capturing app-wide
    //    activity.
    stopSignal.cancel();
  });
}

// Best-effort wait for the system trace engine to initialize before profiling.
// There is no deterministic start callback; tune this for your target devices.
private static final long STARTUP_PADDING_MS = 100L;

// Single background thread that runs the profiling orchestration.
private final ExecutorService profilingExecutor = Executors.newSingleThreadExecutor();

void heavyOperation() {
  // Background computations to profile.
}

示例代码通过以下步骤设置和管理性能分析会话:

  1. 设置执行器:创建一个非界面 Executor,以便在主线程之外接收性能分析结果。
  2. 处理分析结果:创建 Consumer<ProfilingResult> 回调,以接收来自 ProfilingManager 的已完成结果或错误。
  3. 构建性能分析请求:创建 SystemTraceRequestBuilder 以自定义会话设置(或省略自定义设置以使用系统默认设置):

    • setTag():附加由 ProfilingResult.getTag 返回并附加到输出文件名(最多前 20 个小写字母数字字符和连字符)的调用方提供的标记。
    • setDurationMs():设置最长录制时长(以毫秒为单位,例如 60000 表示 60 秒)。
    • setBufferFillPolicy():定义缓冲区如何处理容量限制:BufferFillPolicy.RING_BUFFER 会在缓冲区满时覆盖最旧的数据,而 BufferFillPolicy.DISCARD 会在缓冲区满后停止记录新数据。
    • setBufferSizeKb():设置录制缓冲区大小(以 KB 为单位)。
  4. 可选:提前停止录制:传递 CancellationSignal (stopSignal) 以在目标操作完成后立即停止会话,保存到目前为止录制的数据,并将 ProfilingResult 传递给回调。如需了解详情,请参阅停止正在进行的录制。

  5. 开始并接收结果:调用 requestProfiling() 以开始在后台录制。会话结束时,resultCallback.accept() 会收到 ProfilingResult:

    • 如果成功 (ERROR_NONE),请调用 getResultFilePath() 以获取设备端路径。如需了解如何使用 adb 拉取文件或使用 WorkManager 上传文件,请参阅检索和上传配置文件。
    • 如果失败,请检查 getErrorCode() 和 getErrorMessage()。请求可能会因速率限制以及其他一些原因而失败。
    • 如果您的应用在结果传送之前终止,系统会将结果排入队列,并在您的应用下次使用 registerForAllProfilingResults 注册全局监听器时传送结果。
  6. 添加自定义跟踪点:将操作封装在 trace("...") { ... }(或 Trace.beginSection() / Trace.endSection())中,以标记时间轴的区域。

停止正在进行的录制

如需在互动完成后立即停止捕获,而不是等待 setDurationMs() 过期,请将 CancellationSignal 传递给 setCancellationSignal() 或 requestProfiling()。如果您同时指定了 CancellationSignal 和 setDurationMs(),则以先发生者为准结束会话;如果您未指定任何一个,系统会应用默认时长。

调用 cancel() 不会舍弃录制内容。系统会停止捕获,对截至该时间点收集的数据进行隐去处理并保存,然后通过 ERROR_NONE 将 ProfilingResult 传递给监听器。

通过调用堆栈采样录制系统跟踪记录

在搭载 Android 17(API 级别 37)及更高版本(或搭载更新后的 Profiling Mainline 模块的 Android 16)的设备上,ProfilingManager 支持在单个会话中同时收集采样调用堆栈和系统跟踪记录,以便您查看时间轴事件(例如线程状态、帧和自定义跟踪切片),以及没有显式跟踪插桩的方法的 CPU 调用堆栈。

此示例还展示了如何通过指定一种受支持的配置文件类型 (PROFILING_TYPE_SYSTEM_TRACE) 并传递配置 Bundle,直接调用 ProfilingManager.requestProfiling(不使用 Jetpack 构建器):

Kotlin

val profilingManager = context.getSystemService(ProfilingManager::class.java)

// Pass a Bundle to customize the capture, or pass null for default settings.
val config = Bundle().apply {
    putInt("KEY_DURATION_MS", 15000) // 15 seconds
    putInt("KEY_SIZE_KB", 20480) // 20 MB buffer size

    // Collect sampled CPU call stacks alongside the system trace.
    putBoolean("KEY_COLLECT_STACK_SAMPLING", true)
    putInt("KEY_FREQUENCY_HZ", 100) // Sampling frequency in Hz
    // Optional: Set KEY_SAMPLE_BINDER_ONLY to true to sample only binder calls.
}
profilingManager?.requestProfiling(
    ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE,
    config,
    "trace-with-callstacks",
    null,
    callbackExecutor,
    resultCallback
)

Java

ProfilingManager profilingManager = context.getSystemService(ProfilingManager.class);
if (profilingManager == null) return;

// Pass a Bundle to customize the capture, or pass null for default settings.
Bundle config = new Bundle();
config.putInt("KEY_DURATION_MS", 15000); // 15 seconds
config.putInt("KEY_SIZE_KB", 20480); // 20 MB buffer size

// Collect sampled CPU call stacks alongside the system trace.
config.putBoolean("KEY_COLLECT_STACK_SAMPLING", true);
config.putInt("KEY_FREQUENCY_HZ", 100); // Sampling frequency in Hz
// Optional: Set KEY_SAMPLE_BINDER_ONLY to true to sample only binder calls.

profilingManager.requestProfiling(
    ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE,
    config,
    "trace-with-callstacks",
    null,
    callbackExecutor,
    resultCallback
);

当您在 Perfetto 界面中打开生成的配置文件时,采样的调用堆栈会显示为专用的调用堆栈轨迹,与应用的常规轨迹切片并排显示。选择调用堆栈轨迹的某个区域,即可查看该时间范围内采样调用堆栈的火焰图,如图 1 所示。

Perfetto 界面,显示了包含采样调用堆栈轨迹的系统跟踪、RenderThread 上的跟踪切片,以及所选区域的调用堆栈火焰图。
图 1. Perfetto 界面中结合了调用堆栈采样的系统跟踪。

捕获 Java 堆转储

您可以使用 JavaHeapDumpRequestBuilder(或 PROFILING_TYPE_JAVA_HEAP_DUMP)请求应用的 Java 内存快照。与使用 Debug.dumpHprofData 捕获的标准 .hprof 堆转储不同,ProfilingManager 堆转储旨在实现安全、低开销的生产环境收集:

  • 非冻结收集:系统会创建应用进程的写时复制克隆,并从克隆的进程中收集堆快照,这样在将转储写入磁盘时,应用不会冻结。
  • 仅引用图(无原始对象数据):ProfilingManager 不会像 .hprof 文件那样转储原始内存内容,而是会记录一个 .perfetto-java-heap-dump protobuf 图,其中包含类、对象大小、引用链和垃圾回收 (GC) 根,同时省略了基元值、字符串内容和数组载荷。这样可以保护用户隐私并减小文件大小。如需查看完整细分,请参阅 ProfilingManager 堆转储与 HPROF 转储有何不同。

使用记录系统跟踪记录中所示的相同 Context、Executor 和 resultCallback 设置,使用 JavaHeapDumpRequestBuilder 构建请求:

Kotlin

val heapDumpRequest = JavaHeapDumpRequestBuilder()
    .setTag("memory-leak-check")
    .build()

requestProfiling(
    appContext,
    heapDumpRequest,
    callbackExecutor,
    resultCallback
)

Java

ProfilingRequest heapDumpRequest = new JavaHeapDumpRequestBuilder()
    .setTag("memory-leak-check")
    .build();

Profiling.requestProfiling(
    getApplicationContext(),
    heapDumpRequest,
    callbackExecutor,
    resultCallback
);

如需查看分析 Java 堆转储以诊断内存泄漏的完整端到端演示,请参阅案例研究:调试 OOM 崩溃。

后续步骤

捕获配置文件后,使用 adb 在本地检索它们,或使用 WorkManager 将它们上传到后端。