基于触发器的分析

在 Android 16(API 级别 36)及更高版本中,ProfilingManager 可让您自动捕获配置文件,以响应系统触发器,例如应用无响应 (ANR) 错误、内存不足 (OutOfMemoryError) 崩溃或应用启动事件。由于您无法预测这些事件何时会在现场发生,因此无法提前手动开始录制。借助基于触发器的性能分析,系统会管理捕获过程并将生成的性能分析数据传递给您的应用,以便您:

  • 调试稳定性问题:诊断不可预测的 ANR 和内存泄漏。
  • 优化关键用户历程 (CUJ):分析应用启动(TRIGGER_TYPE_COLD_START 和 TRIGGER_TYPE_APP_FULLY_DRAWN)等流程。
  • 了解用户行为:检查用户发起的应用退出等事件。

如需查看可用触发器的完整列表,请参阅 ProfilingTrigger。

触发器类型

触发器的不同之处在于,当事件发生时,系统如何捕获配置文件。

使用后台跟踪记录的触发器

许多触发器(例如 ANR)都是由先前的问题引起的。仅在系统检测到 ANR 后才开始跟踪,会错过导致 ANR 的阻塞工作。为了捕获导致事件发生的活动,以下触发器会保存系统活跃后台跟踪记录的快照。如果事件发生时没有后台跟踪在运行,则不会收集任何性能分析文件:

触发新捕获的触发器

以下触发器不依赖于后台跟踪。相反,系统会在事件发生时开始新的配置文件捕获:

触发器,其中捕获行为因异常而异

对于以下异常值检测触发器,捕获的配置文件类型(保存活跃后台跟踪记录的快照或开始新的捕获)和速率限制行为取决于系统检测到的特定异常值:

设置触发器

以下代码演示了如何注册 TRIGGER_TYPE_APP_FULLY_DRAWN 触发器并为其设置冷却期。

Kotlin

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun recordWithTrigger() {
    val profilingManager =
        applicationContext.getSystemService(ProfilingManager::class.java) ?: return

    val triggers = ArrayList<ProfilingTrigger>()

    val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN)
        .setRateLimitingPeriodHours(1)

    triggers.add(triggerBuilder.build())

    val callbackExecutor: Executor = Executors.newSingleThreadExecutor()

    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.resultFilePath
            )
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage
            )
        }
    }

    profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

    Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
        // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
        reportFullyDrawn()
    }
}

Java

public void recordWithTrigger() {
  ProfilingManager profilingManager = getApplicationContext().getSystemService(
      ProfilingManager.class);
  List<ProfilingTrigger> triggers = new ArrayList<>();
  ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder(
      ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
  triggerBuilder.setRateLimitingPeriodHours(1);
  triggers.add(triggerBuilder.build());

  Executor callbackExecutor = Executors.newSingleThreadExecutor();
  Consumer<ProfilingResult> resultCallback =
      new Consumer<ProfilingResult>() {
        @Override
        public void accept(ProfilingResult profilingResult) {
          if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.getResultFilePath());
            setupProfileUploadWorker(profilingResult.getResultFilePath());
          } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode="
                    + profilingResult.getErrorCode()
                    + " errormsg="
                    + profilingResult.getErrorMessage());
          }
        }
      };
  profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

  Choreographer.getInstance().postFrameCallback((f) -> {
    // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
    reportFullyDrawn();
  });
}

该代码会执行以下步骤:

  1. 获取管理器:检索 ProfilingManager 服务。
  2. 定义触发器:为 TRIGGER_TYPE_APP_FULLY_DRAWN 构建 ProfilingTrigger。当应用在 activity 发起的冷启动期间调用 reportFullyDrawn() 以报告其已完成启动并可供互动时,会发生此事件。
  3. 设置触发器冷却期:除了系统速率限制器之外,还使用 setRateLimitingPeriodHours 为此特定触发器应用 1 小时的冷却期。此冷却时间可防止应用每小时记录多个启动配置文件。
  4. 注册监听器:调用 registerForAllProfilingResults 以定义用于处理结果的回调。此回调通过 getResultFilePath() 接收已保存配置文件的路径。
  5. 添加触发器:使用 addProfilingTriggers 向 ProfilingManager 注册触发器列表。
  6. 触发事件:发布一个 Choreographer 帧回调,该回调会在绘制下一帧后调用 reportFullyDrawn(),从而向系统发出 TRIGGER_TYPE_APP_FULLY_DRAWN 事件,触发配置文件收集(假设系统后台跟踪正在运行且速率限制器配额可用)。为使此触发器发生,请确保您的应用调用 reportFullyDrawn()。

触发器生命周期

已注册的触发器会在应用重启和系统重新启动后保留。您无需在每次应用启动时或系统重新启动后重新注册触发器。使用 addProfilingTriggers 注册触发器后,系统会自动为您的应用维护这些触发器,直到您将其移除或应用被卸载。

何时取消注册触发器

由于已注册的触发器会在应用重启和系统重启后保留,因此在后续启动时跳过 addProfilingTriggers 不会停止在上一会话中注册的触发器。如需停止捕获特定事件或所有事件的配置文件,请明确调用 removeProfilingTriggersByType 或 clearProfilingTriggers。

在以下情况下,请取消注册触发器:

  • 定位同类群组或回滚远程标志:您可以使用远程配置或 A/B 测试标志对同类群组进行细分分析。每当设备离开抽样同类群组或标志关闭时,取消注册触发器。
  • 完成调试活动:收集到足够的配置文件来诊断问题或发布修复程序后,请移除触发器。取消注册不需要的触发器可节省设备存储空间,并为其他性能分析请求保留应用的速率限制器配额。

回调生命周期

与直接性能分析请求不同,基于触发器的性能分析仅向注册了 registerForAllProfilingResults 的全局监听器提供结果。在回调中,调用 ProfilingResult.getTriggerType 以确定是哪个触发器启动了捕获,并调用 ProfilingResult.getResultFilePath 以找到保存的配置文件。如需详细了解触发器配置文件名、使用 adb 提取文件以及在后台上传结果,请参阅检索和上传配置文件。

虽然触发器在应用重启和系统重新启动后仍会保留,但结果回调不会保留。应用启动完成后,在后台 Executor 上注册回调(这样就不会减慢冷启动速度),以便在以下两种情况下接收配置文件:

  • 在应用运行期间:对于不会导致应用停止或崩溃的触发器,当触发器发生且配置文件已保存时,系统会在提供的 Executor 上调用您的回调。
  • 应用终止后:对于通常涉及或先于应用终止的触发器(例如 TRIGGER_TYPE_ANR、TRIGGER_TYPE_OOM 或 TRIGGER_TYPE_KILL_RECENTS),或者如果您的应用在捕获完成之前停止,系统会在后台保存配置文件,并在应用重启和系统重新启动时将结果排入队列。系统会将未传送的排队结果最多保留 7 天,并在您的应用下次运行并调用 registerForAllProfilingResults 时传送这些结果。

后台跟踪的工作原理

为了在性能分析开销与捕获有用工件之间取得平衡,系统不会持续运行后台跟踪记录。而是通过以随机间隔启动后台跟踪来定期采样。

系统触发的后台跟踪使用具有系统定义的最大大小的环形缓冲区。当缓冲区填满时,新的跟踪记录数据会覆盖最旧的数据。如图 1 所示,当注册的触发器在后台跟踪处于活跃状态且速率限制器配额可用时触发,系统会保存包含导致触发器触发的最新活动的环形缓冲区快照。

显示后台跟踪记录快照工作原理的图表,其中环形缓冲区在触发事件之前捕获数据。
图 1. 后台跟踪记录快照的工作原理。

设置特定于触发器的冷却时间

高频触发器可能会很快消耗应用的速率限制器配额。为防止单个触发器类型耗尽您的配额,请使用 setRateLimitingPeriodHours 在系统速率限制器的基础上为每个触发器添加最短冷却期。

在本地调试触发器

由于后台跟踪在生产环境中以随机间隔运行,因此在本地测试触发器需要强制执行连续后台跟踪,并绕过软件包的系统级速率限制器(使用 setRateLimitingPeriodHours 设置的任何每个触发器冷却时间仍然适用,而 rate_limiter.disabled 仅适用于应用驱动的 requestProfiling 调用)。

如需了解用于强制执行后台跟踪、从命令行模拟触发器以及检查服务状态的 adb 命令,请参阅用于本地性能分析的调试命令中的强制启用后台跟踪以进行触发器测试。