Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ProfilingManager

做性能排查的人大概都遇到过这个空档:线上反馈堆里只有一句“卡了”,问题发生的那一刻没有任何分析工具开着。ProfilingManager 就是为补这个空档来的。Android 15 起,应用可以主动请求四种采集:system trace(系统时间线)、heap dump(堆快照)、heap profile(堆分配采样) 和 stack sampling(周期性调用栈采样);Android 16 又加了 system-triggered profiling(由系统事件触发采集),让系统在 ANR、进程被杀这类事件发生时自动留下一份现场。我们接入时要分清三件事:显式请求怎么发、哪些 trigger 要哪个版本、结果从哪里收。

源码基线:AOSP android-17.0.0_r1(Android 17 / API 37)。ProfilingManager 是 Mainline(可脱离完整系统升级包单独更新的模块) 模块,可经 Google Play system update(Google Play 系统更新) 独立更新,设备上的实际行为可能与这个 AOSP tag 的源码有差异。

从待回答的问题选择采集类型

我们先把问题问对,采集类型自然就定了。ProfilingManager 的四种显式请求,AndroidX 各给了一个 builder——通过连续调用 set*() 方法组装参数的配置对象。四种请求共用同一套结果回调,产物和分析工具却各不相同:

AndroidX builder适合回答的问题产物builder 专有参数读数边界
SystemTraceRequestBuilder卡顿发生在哪条线程,启动阶段被谁阻塞,Binder(跨进程调用)、调度和渲染如何交叠Perfetto system tracesetDurationMs()、setBufferSizeKb()、setBufferFillPolicy()只记录配置允许的 Perfetto 数据源,不能直接给出 Java 对象引用链
JavaHeapDumpRequestBuilderJava 对象、类型和大小如何分布,哪些对象可由 GC Root(垃圾回收根对象)到达Perfetto HeapGraph;固定 r1 后缀 .perfetto-java-heap-dumpsetBufferSizeKb()单次对象图快照,可能含不可达对象;不是普通 HPROF,也不是 native 分配时间线
HeapProfileRequestBuilder哪些调用栈持续产生内存分配,分配量如何分布Perfetto heap profile;固定 r1 后缀 .perfetto-heap-profilesetDurationMs()、setBufferSizeKb()、setSamplingIntervalBytes()、setTrackJavaAllocations()采样只记录部分分配,不能代替 heap dump 的完整对象关系
StackSamplingRequestBuilder哪些调用栈频繁被采样命中,热点大致集中在哪里Perfetto stack sample;固定 r1 后缀 .perfetto-stack-samplesetDurationMs()、setBufferSizeKb()、setSamplingFrequencyHz()按频率抽取的样本是统计近似值,不能当作逐方法精确 CPU 时间

按这张表选型:要线程时间线就采 system trace;要对象存活关系就用 Java heap dump;追持续分配问题用 heap profile;只要低开销的热点线索,就选 stack sampling。采样的用处是把范围缩小,下结论还是要回到 trace 时间线、heap 引用关系或可复现的测试上核对。

heap profile 默认采的是 native allocation(C/C++ 等原生堆分配)。想看 Java/Kotlin 对象的分配,要开 setTrackJavaAllocations(true),同一请求只覆盖其中一类。

再往上看公共参数:四个 builder 都继承 setTag() 和 setCancellationSignal()。tag 用来标识单次采集,放短场景码合适,完整工单信息交给服务端的映射去查。持续采集可以由 CancellationSignal 提前叫停,时长和取消信号都能触发停止请求;不过取消是异步操作,实际停止点、覆盖到的业务范围、结果是否可用,都要以回调为准。Java heap dump 是某一时刻的快照,所以 builder 里没有 durationMs 参数。

API 层与版本判断

接入这个 API,我们第一步把版本范围搞清楚。平台类 android.os.ProfilingManager 是 Android 15(API 35)加入的;AndroidX Core 1.15.0 首次提供了 androidx.core.os.Profiling 和四个 request builder,免去了直接拼装平台参数的工作,本轮核验的稳定版 1.19.1 仍提供这些封装。注意 AndroidX 回调里的结果类型仍是 android.os.ProfilingResult。

显式请求只要求 API 35。系统触发能力分成 API 36、Android 16 minor release 1(36.1)、API 37 三档。36.1 属于 minor SDK release(主 API 级别内的次要 SDK 版本),不属于通过扩展版本号发布能力的 SdkExtensions B Extension;下面的守卫用 SDK_INT_FULL 把 API 37 基线和 Android 16 minor release 1 分开处理。

boolean hasProfiling36_1 =
        Build.VERSION.SDK_INT >= 37
        || (Build.VERSION.SDK_INT >= 36
            && Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1);

这段守卫这样读:API 37 的设备已经包含这组接口,直接走第一个分支;Android 16 设备则要用 SDK_INT_FULL 查询带 minor release 序号的完整 SDK 版本。能力探测就认这套判断——版本字符串、机型名单、系统更新日期这几条捷径都到不了 minor release 这一层,编译期也要用包含相应 minor release API 的 SDK。详细的 36.1 判断见下文“System-triggered profiling 的运行方式”。

发起一次显式请求

我们动手发一次请求。下面的 Java 示例采集 5 秒 system trace,并允许业务场景结束时提前取消;enqueueProfileForUpload() 和 recordProfilingFailure() 代表应用自己的持久化与日志函数。

CancellationSignal stopSignal = new CancellationSignal();

ProfilingRequest request = new SystemTraceRequestBuilder()
        .setBufferSizeKb(10_240)
        .setDurationMs(5_000)
        .setBufferFillPolicy(BufferFillPolicy.RING_BUFFER)
        .setCancellationSignal(stopSignal)
        .setTag("scroll-jank")
        .build();

Profiling.requestProfiling(context, request, executor, result -> {
    if (result.getErrorCode() == ProfilingResult.ERROR_NONE) {
        String path = result.getResultFilePath();
        if (path != null) {
            enqueueProfileForUpload(path, result.getTag());
        }
    } else {
        recordProfilingFailure(
                result.getErrorCode(),
                result.getErrorMessage(),
                result.getTag());
    }
});

类名归属先分清:Profiling、ProfilingRequest、SystemTraceRequestBuilder 和 BufferFillPolicy 来自 androidx.core.os,CancellationSignal 与 ProfilingResult 来自 android.os。请求是异步执行的:方法返回那一刻,采集可能还没开始,最终成败也还悬着,这两件事都要等回调;服务还可能因配置、并发或配额拒绝。加上 API 并没有“已经开始录制”回调,如果目标是某段业务代码,启动就要留出余量,并用 androidx.tracing.Trace 打上可搜索的 slice(带开始、结束时间的时间线区段),业务段结束后再调 stopSignal.cancel() 请求停止。

本次请求要用专用回调的话,executor(回调执行器)和 request-specific listener 要成对提供——这组回调以两者齐全为前提,所以全局 listener 要事先注册好。固定 r1 在回调列表为空时会同步抛出 IllegalArgumentException,这种异常发生在调用点,光等失败回调是等不到的;显式请求推荐传入完整组合,并在调用点处理同步异常。回调线程里只做轻量处理,文件压缩、网络上传这类活交给受约束的后台任务。

BufferFillPolicy 只有两个公开值

BufferFillPolicy 只在 SystemTraceRequestBuilder 上有,两个值我们对着场景选:

策略缓冲区写满后的动作适用窗口
RING_BUFFER环形缓冲区写满后,新事件覆盖最旧事件关注停止点前的滑动卡顿、输入延迟或手动结束场景
DISCARD新事件被丢弃,已有事件继续保留关注采集开始处的冷启动早期事件

选哪个策略,改动的只是保留哪一段时间线,数据源集合保持原样。ProfilingManager 暴露的是受控配置,builder 里有哪些参数,应用就能传哪些;缓冲区大小、时长和采样频率到了系统服务侧,还会按 DeviceConfig(系统可动态调整的配置项) 给出的范围裁剪。android-17.0.0_r1 的默认值与上限适合读源码时参考,跨设备的产品常量要另外标定。

缓冲区满时保留开头还是近期活动

图:RING 保存靠近停止点的近期活动,DISCARD 优先保留采集开头。 条长为示意;允许的数据源集合保持原样,请求的 buffer / 时长以服务端按范围裁剪后的结果为准。

两类 listener 与结果再次投递

结果从哪收?平台有两条通道:request-specific listener 和 global listener(接收当前 UID 下全部 profiling 结果的全局监听器),后者由 AndroidX 的 Profiling.registerForAllProfilingResults() 注册,能收到当前 UID 下全部 profiling 结果(UID 即 Android 标识应用沙箱的 Linux 用户 ID)。两条通道的分工如下:

请求来源request-specific listenerglobal listenergetTriggerType()
显式 requestProfiling()完整配置后收到注册后也可收到同一结果API 36+ 为 TRIGGER_TYPE_NONE;API 35 不调用该 getter
system-triggered profiling没有此通道收到对应的 trigger 常量

两条通道一起用时,我们让 request-specific listener 负责更新本次交互状态,global listener 负责持久化结果文件。成功结果用平台返回的完整 resultFilePath 字符串去重即可;失败结果要先按非成功 errorCode 处理,路径放到一边:API 合同规定失败路径为 null,而固定 r1 的部分服务结果回调直接拼接 getAppFileDir() + resultFile,可能形成以 null 结尾的字符串,所以路径非空推不出成功。ProfilingResult 也没有唯一结果 ID,直接拿 triggerType + tag + errorCode 当唯一键,会把两次独立失败误合并成一条。失败也要入库的话,固定一个写入通道,并把显式请求的内部 request ID 记在应用自己的请求记录里。

还有一类延迟到达的结果:采集完成前进程退出了,平台可在应用下次启动并注册全局 listener 后再次投递。所以全局 listener 要在进程启动早期注册,并在整个进程生命周期内保持可用。trigger 配置倒不用每次都加——系统服务 ProfilingService 会把它写入磁盘恢复;listener 属于进程内回调,每次都要重新注册。另外相同 trigger type 只保留一份配置,后添加的会替换旧的。

结果文件、字段与隐私

结果拿到手了,我们先看文件本身。成功时,文件位置只认 ProfilingResult.getResultFilePath() 返回的这一条路径,读取、上传完成后删除、重试和留存超时都围绕它实现;外部存储权限也一并省掉,files/profiling 之类的目录无需写进代码。

字段方面,ProfilingResult 在 API 35 提供 errorCode、errorMessage、resultFilePath 和 tag,triggerType getter 从 API 36 加入,profilingType getter 则一直缺席。所以显式请求的采集类型,要在提交请求时就写进应用自己的记录;trigger 结果按 trigger 与产物的对应规则映射。文件扩展名只能区分一部分类型,剩下的靠记录补齐——多个 profile 都可能装在 Perfetto trace 容器里。

上传之前,隐私这关必须过。system trace 会移除其他应用和进程的信息,所以 PerfettoSQL(Perfetto 的 SQL 查询接口) 能查到的数据范围比本地完整 trace 小;但系统的 redaction 针对的是无关进程,我们自己应用的线程名、trace slice、Surface 名或业务 tag 依旧可能原样留在文件里。Perfetto HeapGraph 主要保留对象身份、类型、大小和引用关系,和普通 HPROF 的完整字段值 / 字符串转储语义是两回事:它仍可暴露类型名、字段名、代码位置和对象结构,特定对象还有 NAR 大小、Bitmap 尺寸等专门元数据。heap profile 与 stack sampling 同样可能带出类名、方法名及调用路径。所以上传前,我们按数据类型把授权、加密、访问控制、保留期限和删除策略各过一遍。

同理,tag 里只放不含用户含义的场景码,由服务端受控映射到内部工单;手机号、订单号、账号、Token、URL 查询参数、地理位置、明文会话标识,这些统统留在服务端那侧。

结果文件生命周期

文件是怎么写进去的?系统先让应用进程在 files/profiling/ 中创建目标文件,再通过文件描述符(跨进程传递已打开文件的句柄)把结果写进去。所以 ProfilingResult.resultFilePath 返回完整路径时,文件已经在那里了,callback 拿到路径直接用就好;应用要做的是在一次数据库事务(相关字段要么全部写入,要么全部回滚)里登记路径与业务 metadata(描述文件来源和状态的附加字段),然后交给后台任务。

整条处理链我们建议按下面的时序走:

sequenceDiagram
    participant App
    participant PM as ProfilingManager
    participant Store as App 私有存储
    participant Upload as 上传任务
    participant Server as 诊断平台

    App->>PM: requestProfiling(...) / trigger registration
    PM-->>App: ProfilingResult
    App->>Store: 落 metadata 与文件引用
    Upload->>Store: 校验 Wi-Fi / 充电 / 文件大小 / 预算
    Upload->>Server: 上传产物 + metadata
    Server-->>Upload: 返回 sample id
    Upload->>Store: 删除文件或标记已归档

注意三个入口——回调重复、进程重启、上传重试——都应写入同一条幂等记录;只有后端确认完整接收后才删除本地文件,上传失败的保留到应用自己的过期时间或容量上限。

metadata(描述这份文件来源和状态的附加字段) 至少包含:

  • case_id / session_id
  • request_type / trigger_type
  • app_version / build_id(构建标识)/ device / sdk_int_full
  • 页面、前后台状态、实验分组、触发原因
  • result_file_path、文件大小、内容摘要(用于校验完整性)、上传状态和清理时间

系统侧也会做一点清理。固定 android-17.0.0_r1 里,oldFilesCleanup 功能开关启用时,ProfilingManager 会借用应用提供的 executor,尝试删除文件系统最后修改时间早于 5 天阈值的旧文件;清理频率由该 manager 实例内的 mLastCleanupMs 控制,通常每天至多一次,新实例会重置这项状态,而且触发点依赖相关 API 调用,服务侧另有结果重投与过期处理。所以这层清理我们只当后备:调用时机、系统配置、Profiling Mainline 模块或 OEM(设备厂商) 配置都可能变,空间预算、容量上限和保留期要握在应用自己手里——上传成功后立即删除,磁盘不足时按年龄淘汰。

回到 tag:平台会把它转成小写、过滤到字母、数字和连字符,再截取前 20 个有效字符写进文件名——这只约束文件名里的那一段,回调返回的原始 tag 原样保留,所以 tag 本身仍只放短场景码或随机诊断任务 ID。

Java Heap Dump 的敏感数据风险

我们先辨认格式:本节 JavaHeapDumpRequestBuilder 在固定 r1 使用 android.java_hprof,由 ART 的 Perfetto 写出器生成 HeapGraph,和 Debug.dumpHprofData() 或传统 HPROF 是两种东西。常规对象记录的是类型、大小和引用;登录 Token、字符串正文、密钥字节或所有 primitive 字段值,并不保证原样写出。特殊类型可以额外带 NAR 大小、Bitmap 尺寸等有限元数据,具体以 protobuf 和设备实现为准。

对象图仍能暴露内部类名、引用字段名、代码位置及结构关系,与同文件中的其他数据一起可能揭示业务实现。若另行接入原始 HPROF,字段、数组和字符串内容的风险要另外评估——两种格式各配一套脱敏规则与分析器,使用时分开。 system trace 的 redactor 不等于覆盖所有产物的数据审查,HeapGraph 与其他 heap 数据仍按真实字段和用途评估。方案按分析方式选:

  1. 优先在设备上提取:若问题能由对象计数、Retained Size(对象及其支配对象占用的内存)、类分布或少量调用栈回答,可在设备上提取诊断摘要,只上传摘要。
  2. 受控后端分析:必须上传原始 heap dump 时,使用 TLS 加密传输、服务端加密存储、独立 KMS(密钥管理系统)、最小权限、访问审计和短保留期。后端要分析文件,就必然存在受控的解密路径;“端到端加密、服务端读不到原文”和服务端直接分析,两者只能取其一。
  3. 明确授权与采样范围:隐私告知应说明性能诊断可能包含内存快照,远程开关要能关闭 heap dump,并把采样限定到必要版本和必要用户范围。
  4. 备份排除:files/ 默认可能进入 Auto Backup。把 files/profiling/ 同时排除在云备份和设备迁移规则之外,并兼顾旧版 fullBackupContent 配置。
  5. 删除可验证:上传成功、超过保留期、用户撤回同意或诊断任务关闭时,都要有可观测的删除记录。

最后,heap dump 与通用 trace 上传接口要分开:两者的数据敏感度、访问人群、保留期和审计要求各不相同。

System-triggered profiling 的运行方式

我们再看系统触发这一侧。ANR、APP_FULLY_DRAWN 及部分 kill/request-running 触发器复用周期性、随机启动的后台 ring-buffer trace;事件发生时,系统要先检查注册与限流,后台 trace 也得正在运行,才谈得上请求 running trace snapshot。这套前提只适用于这一类触发器:COLD_START 另开新采集,OOM 请求 Java HeapGraph,ANOMALY / APP_COMPAT 按子类型走各自的路径。注册表达的是允许采样,事件是否留下文件,要看当时的各项条件。

历史快照、新冷启动与 OOM 的触发分流

图:多数时序触发器复用后台 trace,COLD_START 新开采集,OOM 使用 Java HeapGraph。 每条路径都需有效注册及相应条件;anomaly / app-compat 按子类型处理,后台 trace 在跑只是其中一类前提。

trigger 按版本分层,下表同时列出逐项的产物与触发语义:

版本trigger产物与触发语义
API 36TRIGGER_TYPE_APP_FULLY_DRAWN冷启动调用 reportFullyDrawn() 后的 running system trace snapshot;不泛指每次 Activity 完整绘制
API 36TRIGGER_TYPE_ANRANR(Application Not Responding,应用无响应)时的 running system trace snapshot
version 36.1TRIGGER_TYPE_APP_REQUEST_RUNNING_TRACE应用调用 requestRunningSystemTrace(tag) 时保存当前 running trace;此前需注册此 trigger 或全部 trigger
version 36.1TRIGGER_TYPE_KILL_FORCE_STOP用户在设置页强行停止应用时的 running trace snapshot
version 36.1TRIGGER_TYPE_KILL_RECENTS用户从最近任务移除应用时的 running trace snapshot
version 36.1TRIGGER_TYPE_KILL_TASK_MANAGER用户通过任务管理界面停止应用时的 running trace snapshot
API 37TRIGGER_TYPE_OOMJava OOM(out of memory,内存不足)对应的 Java heap dump
API 37TRIGGER_TYPE_ANOMALY产物随 anomaly(系统识别的异常)子类型变化,tag 携带子类型信息
API 37TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE因 ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE 结束进程时的 running trace snapshot
API 37TRIGGER_TYPE_COLD_START为本次冷启动新开的 system trace 与 stack sampling
API 37TRIGGER_TYPE_APP_COMPAT产物随兼容性问题类型变化,tag 携带问题信息

注册入口也有版本分层:addProfilingTriggers() 从 API 36 可用,addAllProfilingTriggers() 与 requestRunningSystemTrace() 属于 version 36.1。trigger 结果由全局 listener 接收;普通 trigger 的 rateLimitingPeriodHours 是额外最小间隔,一般还受整机 / UID 配额限制。固定 r1 对 anomaly 另有分支:多数 anomaly 绕过这套通用限流,memory-limit anomaly 使用专用限流——做预算和写文档时,把触发器按配额规则分组描述。

两个启动 trigger 的时间窗口

启动排障最常用的是两个 trigger,我们先分清它们各自的时间窗口。APP_FULLY_DRAWN 在冷启动完整绘制后请求保存当时已运行的 system trace:它依赖后台 trace 恰好在跑,主要保留触发前的近期 ring-buffer 活动,适合看启动尾段;快照请求异步执行,结束点以实际 trace 时间为准,可能偏离事件时刻。

COLD_START 正相反,它是 API 37 为这次冷启动新开的一份 system trace,同时做 stack sampling:应用调用 reportFullyDrawn() 时停止,没调用就默认约 5 秒停止;它的 system trace 用 DISCARD,为的是留下采集开头。还要记得启动 trace 自身可能给启动带来延迟,做性能阈值判断时把这部分开销算进去。

两个 trigger 的采集来源、起点、停止点和产物都不同,所以分析启动问题时,样本记录里要保留 trigger type,两类时间窗口各算各的分位数(如 P50、P95)。

两个启动 trigger 的不同时间窗口

图:APP_FULLY_DRAWN 取已有记录的近期窗口,COLD_START 尝试为本次启动新开采集并保留早期事件。 条长不是实测;两者都可能有延迟。冷启动默认时长约 5 秒且可配置,启动最早的指令可能落在窗口之外,两种样本分开统计。

三类排障场景

交互卡顿

落到具体场景。滑动掉帧、输入延迟和动画停顿,可以采 3~5 秒 system trace;若用户操作结束时取消采集,RING_BUFFER 有利于留下结束点之前的线程活动。Perfetto 里我们检查主线程、RenderThread(渲染线程)、Frame Timeline(逐帧生命周期与卡顿标记)、Binder(Android 的跨进程通信机制)、CPU 调度与自定义业务 slice,把掉帧时刻与调用栈或锁等待对上。

冷启动与 ANR

冷启动回归可以把 COLD_START、APP_FULLY_DRAWN 与手动 system trace 结合起来,样本必须按 trigger type 分组。ANR trigger 用于补充现场,完整的事件记录仍要靠 Play Console、ApplicationExitInfo、ANR trace 与业务日志,它自己则受后台 trace 和限流约束;延迟到达和进程重启后再次投递的文件,由全局 listener 接住。

内存持续增长

内存持续增长的排查分两步:分配来源未知时先用 heap profile 找高频分配栈,再用 Java heap dump 检查存活对象和 GC Root。API 37 的 TRIGGER_TYPE_OOM 只覆盖 Java OOM,LMKD(low memory killer daemon,系统低内存杀进程机制) 和 native OOM(C/C++ 等原生堆耗尽) 要另走排查路径。另外,应用装了自定义 UncaughtExceptionHandler(未捕获异常处理器) 的话,要继续调用默认 handler,OOM trigger 的处理流程才能按平台预期走完。

覆盖全部失败结果

失败结果我们也要全部接住。android-17.0.0_r1 的 ProfilingResult 定义了八种失败码,成功码是 ERROR_NONE:

错误码表示的阶段应用侧处理
ERROR_FAILED_RATE_LIMIT_SYSTEM系统配额拒绝本次采集记录为未采样,退避(逐步拉长重试间隔);不要在前台循环请求
ERROR_FAILED_RATE_LIMIT_PROCESS应用 UID 预算耗尽(名称仍叫 PROCESS)降低主动请求频率,并复核 trigger 的间隔配置
ERROR_FAILED_PROFILING_IN_PROGRESS已有 profiling 任务运行对主动请求做串行调度,稍后重试一次
ERROR_FAILED_EXECUTING底层 profiler 启动或执行失败保存系统版本、类型、tag 和消息,按版本聚合
ERROR_FAILED_POST_PROCESSING采集后的处理或文件准备失败文件不可用,按版本与 profile 类型聚合
ERROR_FAILED_NO_DISK_SPACE没有足够空间写出结果清理已处理文件,设置磁盘剩余空间阈值后再请求
ERROR_FAILED_INVALID_REQUEST参数组合或请求内容无效停止重试,修正 builder 参数或版本分支
ERROR_UNKNOWN未归入以上类别的失败保存 errorMessage 与运行环境,限制重试次数

失败日志至少保存应用自己的 request ID、profile 类型、triggerType、tag、errorCode、errorMessage 和平台版本。判断顺序是先看 errorCode == ERROR_NONE,再检查文件路径及可读性;失败路径即使非空,也不拿来读文件。系统触发的前提是注册、后台窗口、限流这些条件都过关,哪一项没过,应用可能连结果回调都收不到——这张表对应的是有回调的那部分失败。

关于限流,再多说两句。次数、时长上限和缓冲区上限来自可更新模块与 DeviceConfig;这些服务端配置并不是普通应用可以依赖读取的公开能力,产品侧用保守的本地策略,并准备好应对实际的拒绝或裁剪。实验设备上,adb shell device_config list profiling 可以看当前配置;adb shell device_config put profiling_testing rate_limiter.disabled true 在通用 RateLimiter 中只为显式请求绕过限流;系统 trigger 的测试包开关 system_triggered_profiling.testing_package_name 会强制该包的后台 trace,并对该包绕过通用系统限流——普通 trigger 的应用自定间隔仍另行检查,memory-limit anomaly 还有特殊分支。设置时用实际包名。改前记录相关 key 的原值及是否存在,结束后按原值恢复:原本存在的 key 写回原值,原本不存在的 key 删除即可,一律置 false 并不算恢复。

限流比“每小时几次”这个印象细得多:通用限流对显式请求按 profiling 类型计成本,对 system-triggered 请求使用触发式成本;再检查小时、天、周窗口以及应用 UID 与整机预算,任何一项预算见底都可能拒绝请求。应用侧的对策是设更保守的本地冷却时间、保证同类采集串行——额度花完只能等窗口重置,循环重试刷不回来。

普通请求、trigger 与 anomaly 的限流差异

图:通用配额按成本和小时、天、周检查 UID 与整机预算,普通 trigger 再加应用自定间隔。 固定 r1 的多数 anomaly 绕过通用策略,memory-limit anomaly 另有限流;不能把三条路径画成同一固定次数门槛。

与其他工具的边界

选型之外还要看分工:ProfilingManager 和几件常用工具各在哪一环,我们对着下表过一遍:

工具使用位置能力边界
ProfilingManager量产设备、线上回归、系统事件采样受完整平台 SDK 版本、后台 trace 是否运行和限流约束
Android Studio Profiler开发机交互分析与即时验证很难覆盖用户现场和系统事件发生前的窗口
adb / Perfetto CLI实验室压测、定制数据源、自动采集普通应用无法在量产用户设备上任意执行
APM(Application Performance Monitoring,应用性能监控)SDK业务指标、崩溃信息、长期统计是否包含 system trace、heap 数据取决于 SDK,数据责任由接入方评估

ProfilingManager 生成诊断材料,触发策略、样本元数据、传输、访问权限、留存和删除仍由监控系统负责;线上指标异常用来定位样本,profile 用来解释某次现场——两种数据各答各的问题。

设备验证与旧版本降级

最后我们看上线后的验证。Mainline 模块、厂商 Perfetto data source(数据源)、系统负载、存储与后台 trace 抽样都会影响成功率,所以设备覆盖表至少要记录 build fingerprint(精确标识系统构建的字符串)、完整 SDK 版本、Profiling 模块版本、请求或 trigger 类型、错误码、文件大小和可查询 schema(表与字段结构)。某一品牌成功率偏低时,先按这些字段聚合,再判断是不是 OEM 差异;单次没产出文件,先把当时的后台 trace、配额和限流查一遍,再怀疑 trigger 本身。

测试设备上,官方 profiling_testing DeviceConfig 开关可以维持目标包的后台 trace、关闭 rate limiter(限流器) 或保留临时未裁剪结果——它们只服务隔离测试,实验结束必须恢复,生产代码对这些开关零依赖。分析量产环境的 redacted(已脱敏) system trace 时记得标记来源:跨进程表或 SQL 为空,可能是采集窗口没覆盖到,也可能是 redactor(脱敏器) 删掉了无关进程。

低版本怎么办?Android 8—14 没有等价的线上 profile API,降级的目标是保住可比较指标和复现线索:启动/帧耗时用应用计时、FrameMetrics/JankStats 与 Android vitals,业务慢操作用名称稳定、低基数的 Trace slice,API 30+ 的崩溃/ANR 可用 ApplicationExitInfo 配合诊断平台;Android 8—10 需保留应用自有记录与其他可用诊断线索,本地重现再上 Perfetto、Profiler、heap dump 或 Macrobenchmark。注意 Trace.beginSection() 只写事件,本身不会启动或保存 system trace。

源码调用链核对

android-17.0.0_r1 中可以沿以下路径核对关键行为:

源码位置可验证的行为
framework/java/android/os/ProfilingManager.java双层 listener、无 listener 时丢弃请求、files/profiling/、重投提示、旧文件清理
framework/java/android/os/ProfilingTrigger.javaAPI 37 trigger 语义、冷启动 5 秒边界、OOM 默认 handler 要求、应用侧限流
framework/java/android/os/ProfilingResult.java错误码、路径字段、tag 与 triggerType;失败路径为 null 是 API 合同,实际回调还需核对 Manager 的拼接实现
service/java/com/android/os/profiling/Configs.java参数裁剪、默认范围、Perfetto 数据源和缓冲区策略
service/java/com/android/os/profiling/ProfilingService.java限流、执行与后处理、脱敏、文件后缀、结果复制和回调

把调用链串一遍:应用侧 request 经 Binder 进入 ProfilingService,服务依次检查并发状态、限流与参数,再启动 Perfetto。采集完成后,system trace 与受开关控制的 stack sampling 按路径经过 redaction(删除或合并其他进程的信息);服务请求应用进程创建目标文件,并取得可写文件描述符,复制临时结果后发送 ProfilingResult。可回传的失败映射为错误码,但同步异常、未命中的 trigger 或部分拒绝未必伴随结果回调,调用返回与采集成功也是两回事。

请求检查、采集、脱敏与 FD 交付

图:应用请求进入服务后先检查身份、类型、并发与配额,再按配置采集和处理产物,最后复制到应用文件。 图示为主要显式请求路径;system trace 和受开关控制的 stack sampling 按路径脱敏,部分异常或 trigger 拒绝不保证应用回调。

上线前检查清单

  • API 35、API 36、version 36.1、API 37 的代码路径分别受运行时能力保护。
  • 显式请求成对提供 executor 与 listener,或确保全局 listener 已注册。
  • 全局 listener 在进程启动早期注册,并能处理重投和重复成功回调。
  • 成功文件按 resultFilePath 去重;失败只由一个通道持久化。
  • profile 类型在提交请求时记录,不从 ProfilingResult 或扩展名猜测。
  • tag 使用短场景码,不含用户标识和业务明文。
  • 上传、删除、磁盘水位、退避和保留期限都有可测试策略。
  • system trace 的脱敏范围、heap 数据风险和用户授权已经过隐私评审。
  • trigger 命中率按采样能力解释,不把“无文件”直接当作“事件未发生”。

参考资料

  1. App-driven profiling:显式请求、回调与取消
  2. Trigger-based capture:后台 trace、触发与调试
  3. Retrieve and analyze:结果文件与应用侧处理
  4. Querying profiles:脱敏 trace 的查询边界
  5. androidx.core.os.Profiling API
  6. android.os.ProfilingManager API
  7. android.os.ProfilingTrigger API
  8. android.os.ProfilingResult API
  9. 本轮固定 AndroidX Profiling.kt 源码
  10. AOSP Profiling 模块:android-17.0.0_r1
  11. AOSP ProfilingManager.java:结果回调与旧文件清理
  12. AOSP ProfilingResult.java:字段与错误码
  13. AOSP ProfilingTrigger.java:trigger 版本与产物
  14. AOSP ProfilingService.java:trigger 与结果持久化
  15. Perfetto 文档
  16. Build.VERSION.SDK_INT_FULL:minor release 版本判断
  17. A17 HeapGraph protobuf:对象、引用与专门元数据
  18. A17 ART Perfetto HeapGraph 写出器

相关章节