在 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. }
示例代码通过以下步骤设置和管理性能分析会话:
- 设置执行器:创建一个非界面
Executor,以便在主线程之外接收性能分析结果。 - 处理分析结果:创建
Consumer<ProfilingResult>回调,以接收来自ProfilingManager的已完成结果或错误。 构建性能分析请求:创建
SystemTraceRequestBuilder以自定义会话设置(或省略自定义设置以使用系统默认设置):setTag():附加由ProfilingResult.getTag返回并附加到输出文件名(最多前 20 个小写字母数字字符和连字符)的调用方提供的标记。setDurationMs():设置最长录制时长(以毫秒为单位,例如60000表示 60 秒)。setBufferFillPolicy():定义缓冲区如何处理容量限制:BufferFillPolicy.RING_BUFFER会在缓冲区满时覆盖最旧的数据,而BufferFillPolicy.DISCARD会在缓冲区满后停止记录新数据。setBufferSizeKb():设置录制缓冲区大小(以 KB 为单位)。
可选:提前停止录制:传递
CancellationSignal(stopSignal) 以在目标操作完成后立即停止会话,保存到目前为止录制的数据,并将ProfilingResult传递给回调。如需了解详情,请参阅停止正在进行的录制。开始并接收结果:调用
requestProfiling()以开始在后台录制。会话结束时,resultCallback.accept()会收到ProfilingResult:- 如果成功 (
ERROR_NONE),请调用getResultFilePath()以获取设备端路径。如需了解如何使用adb拉取文件或使用WorkManager上传文件,请参阅检索和上传配置文件。 - 如果失败,请检查
getErrorCode()和getErrorMessage()。请求可能会因速率限制以及其他一些原因而失败。 - 如果您的应用在结果传送之前终止,系统会将结果排入队列,并在您的应用下次使用
registerForAllProfilingResults注册全局监听器时传送结果。
- 如果成功 (
添加自定义跟踪点:将操作封装在
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 所示。
捕获 Java 堆转储
您可以使用 JavaHeapDumpRequestBuilder(或 PROFILING_TYPE_JAVA_HEAP_DUMP)请求应用的 Java 内存快照。与使用 Debug.dumpHprofData 捕获的标准 .hprof 堆转储不同,ProfilingManager 堆转储旨在实现安全、低开销的生产环境收集:
- 非冻结收集:系统会创建应用进程的写时复制克隆,并从克隆的进程中收集堆快照,这样在将转储写入磁盘时,应用不会冻结。
- 仅引用图(无原始对象数据):
ProfilingManager不会像.hprof文件那样转储原始内存内容,而是会记录一个.perfetto-java-heap-dumpprotobuf 图,其中包含类、对象大小、引用链和垃圾回收 (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 将它们上传到后端。