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


title: APM 全景、Firebase 与商业平台选型 chapter: '17.1' section: '17.1' applicable_versions: Android 8 (API 26) - Android 17 (API 37) last_verified: '2026-08-14' last_verified_against: Android Developers JankStats / FrameMetrics / ApplicationExitInfo / ProfilingManager / ProfilingTrigger / Android vitals / 16 KB docs, AOSP android-17.0.0_r1, kernel android17-6.18-2026-06_r39, GitHub upstream READMEs confidence: medium tags:

  • apm related_chapters:
  • '17.0'
  • '22.10' consolidated_from:
  • src/part3-tools/ch17-apm/19.10-apm-tool-compatibility.md
  • src/part3-tools/ch17-apm/01-apm-landscape.md
  • src/part3-tools/ch17-apm/14-firebase-performance.md
  • src/part3-tools/ch17-apm/15-commercial-apm.md sources:
  • type: official path: https://developer.android.com/topic/performance
  • type: official path: https://developer.android.com/topic/performance/vitals
  • type: official path: https://developer.android.com/jetpack/androidx/releases/metrics
  • type: official path: https://developer.android.com/guide/practices/page-sizes
  • type: official path: https://developer.android.com/reference/androidx/metrics/performance/JankStats
  • type: official path: https://developer.android.com/reference/android/view/FrameMetrics
  • type: official path: https://developer.android.com/reference/android/app/ApplicationExitInfo
  • type: official path: https://developer.android.com/reference/android/os/ProfilingManager
  • type: official path: https://developer.android.com/reference/android/os/ProfilingTrigger
  • type: official path: https://firebase.google.com/docs/perf-mon
  • type: github path: https://github.com/Tencent/matrix
  • type: github path: https://github.com/KwaiAppTeam/KOOM
  • type: github path: https://github.com/bytedance/btrace
  • type: github path: https://github.com/measure-sh/measure
  • type: official path: https://firebase.google.com/docs/perf-mon/get-started-android
  • type: official path: https://firebase.google.com/docs/perf-mon/troubleshooting
  • type: official path: https://firebase.google.com/docs/perf-mon/network-traces
  • type: official path: https://firebase.google.com/docs/perf-mon/screen-traces
  • type: official path: https://firebase.google.com/docs/perf-mon/custom-code-traces?platform=android
  • type: official path: https://firebase.google.com/docs/perf-mon/custom-url-patterns
  • type: official path: https://firebase.google.com/docs/perf-mon/alerts
  • type: official path: https://firebase.google.com/docs/perf-mon/bigquery-export
  • type: official path: https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-bom/maven-metadata.xml
  • type: official path: https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-perf/maven-metadata.xml
  • type: official path: https://dl.google.com/dl/android/maven2/com/google/firebase/perf-plugin/maven-metadata.xml
  • type: official path: https://dl.google.com/dl/android/maven2/com/google/gms/google-services/maven-metadata.xml
  • type: official path: https://docs.sentry.io/platforms/android/
  • type: official path: https://www.volcengine.com/docs/6431
  • type: official path: https://bugly.qq.com/docs/
  • type: official path: https://bugly.tds.qq.com/docs/
  • type: official path: https://github.com/getsentry/sentry-java/releases/tag/8.53.0
  • type: official path: https://github.com/getsentry/sentry-android-gradle-plugin/releases/tag/6.19.0
  • type: official path: https://docs.sentry.io/platforms/android/profiling/legacy/
  • type: official path: https://repo1.maven.org/maven2/io/sentry/sentry-android/maven-metadata.xml
  • type: official path: https://repo1.maven.org/maven2/com/tencent/bugly/bugly-pro/maven-metadata.xml
  • type: official path: https://repo1.maven.org/maven2/com/tencent/bugly_16kb/bugly-pro/maven-metadata.xml status: finalized task9_state: reviewed task2b_state: fixed pipeline_stage: ready-to-publish task6_state: reviewed last_consolidated_at: '2026-08-24'

APM 全景、Firebase 与商业平台选型

APM 客户端采集启动、帧、网络、资源、Crash 和 ANR 等应用可见信号,再由服务端聚合和归因。Firebase 与商业平台的差异主要落在信号覆盖、数据模型、部署成本和生态集成。

客户端信号、采集架构与选型维度

APM 先回答“线上发生了什么”

APM(Application Performance Monitoring,应用性能监控)负责回答三类线上问题:

  • 哪个版本、页面、设备群或实验组正在变差;
  • 影响面有多大,是否值得进入修复队列;
  • 能否保留一份足以继续诊断的现场样本。

APM SDK(Software Development Kit,软件开发工具包)通常只能看到应用有权限采集的信号。一次慢帧可能来自主线程业务、RenderThread、CPU 抢占、Binder 对端、I/O、GPU 或 SurfaceFlinger。RenderThread 是渲染线程,Binder 是 Android 跨进程通信机制,I/O 指输入/输出,SurfaceFlinger 是系统显示合成服务。

一次进程死亡也可能是 Java crash、native crash、ANR、LMK、force-stop、安装更新或系统策略。native crash 指 C/C++ 等原生代码崩溃;ANR(Application Not Responding)是应用无响应;LMK(Low Memory Kill)是低内存终止;force-stop 是强制停止。仅凭一个耗时或 reason(退出原因字段)无法判断根因。

APM 负责发现问题和筛选样本,Perfetto、Android Studio Profiler、simpleperf(CPU 采样工具)、heap dump(堆对象快照)、AGI(Android GPU Inspector)和 dumpsys(系统状态查询命令)负责复现并定位原因。线上系统提供分布和样本,线下及系统工具再把一个样本展开到线程、调用栈、资源、GPU 和显示路径。

Android 17 复核基线

层级版本基线APM 能看到什么
Android 平台Android 17 / API 37 / android-17.0.0_r1ApplicationExitInfoFrameMetricsProfilingManager、系统 trace 与进程生命周期
Android 内核android17-6.18-2026-06_r39scheduler、I/O、binder driver、dma-buf/fence、内存压力等底层事实;普通应用不能任意读取这些数据
Jetpack Metricsandroidx.metrics:metrics-performance:1.0.0JankStats 的帧时长、jank 判断与 UI state
第三方/自研 APM随应用版本固定埋点、采样、堆栈、hook(拦截或替换调用)、缓存、上传和服务端聚合
Google Play当前 Play Console / Reporting API 口径Android vitals 的 crash、ANR、wake lock、启动、渲染等聚合数据

内核表中的 scheduler 是线程调度器,binder driver 是 Binder 的内核驱动,dma-buf 用于跨组件共享缓冲区,fence 用于同步缓冲区的生产者与消费者。这些词描述内核证据,不表示普通应用能直接读取它们。

表中的 jank 指渲染过慢造成的卡顿帧,UI state 是当时的页面与交互状态,wake lock 是阻止设备进入休眠的系统锁。

Android 平台版本与 APM SDK 版本要分别记录。Android 17 不会自动升级应用内的 Matrix、KOOM、JankStats、Firebase 或自研 SDK;升级 SDK 也不会改变设备的 framework(Android 框架层)和 kernel(内核)实现。

从异常指标到可验证结论

下面的流程图展示一条慢帧问题从线上发现到修复验证的证据路径。

flowchart LR
    M["指标异常\nslow_frame_rate 上升"] --> C["上下文切分\n版本 / 机型 / 页面 / 实验"]
    C --> S["样本筛选\nframe data / stack / session_id"]
    S --> T["深证据\nPerfetto / profile / heap dump"]
    T --> R["同版本复现\nMacrobenchmark / 手工场景"]
    R --> F["修复与灰度\n同口径指标回落"]

图中的 slow_frame_rate 是慢帧占比,stack 是调用栈,session_id 用于关联同一次使用会话。它们负责缩小范围,不能单独说明慢帧发生在哪一行代码。

例如,灰度发布(只向一部分用户提供新版本)的看板发现,图片编辑页的慢帧率只在某个版本和一组低内存设备上升。JankStats 样本带有页面、操作和帧时长,但没有主线程阻塞点。团队从该分组挑选可复现设备,采集 Perfetto,看到每次缩放都会在主线程同步解码大图并伴随 I/O 等待;simpleperf 或方法 trace 再把耗时集中的调用定位出来。

修复后,用相同图片、相同操作序列和相同编译模式跑 Macrobenchmark,再在小流量灰度中观察原指标、用于归并同类问题的堆栈特征,以及 crash/ANR 副作用。若只看到慢帧率下降,却更换了分母、采样率或设备范围,这次“改善”不能作为相同统计规则下的结论。

四类能力不要混在一起选

Android 性能工具至少分成四类。分类依据是采集位置、运行阶段、输出证据和工程成本,不能按“都能看性能”合并选型。

类别代表能力运行位置主要输出典型成本
客户端 APMMatrix、KOOM、btrace/RheaTrace、Measure、自研 SDKRelease / 灰度发布聚合指标、异常样本、堆栈、trace、heap 摘要包体、CPU/内存、hook / 插桩(构建时插入采集代码)兼容、隐私、上传平台
官方信号与 SDKAndroid vitals、JankStats、FrameMetrics、ApplicationExitInfo、ProfilingManager、Tracing系统、Play、应用进程平台指标、帧数据、退出记录、受控 profile、应用 slice(带起止时间的 trace 区间)最低 API 版本、指标定义、回调开销、rate limit(频率限制)
线下诊断Perfetto、Profiler、simpleperf、LeakCanary、AGI、dumpsysDebug / QA / 实验室trace、调用栈、heap、GPU 命令流、系统状态需要设备、复现和人工分析
Benchmark / CIMacrobenchmark、Microbenchmark、PerfDog、设备 benchmarkCI / 实验室可比较的耗时、帧、吞吐、功耗或分数测试环境、预热、编译模式、设备与温度控制

Release 是供用户安装的发布构建,Debug 是开发调试构建,QA 是测试阶段,CI(Continuous Integration,持续集成)是在代码变更后自动构建和测试。

客户端 APM 擅长覆盖大量真实设备,却受权限与采样预算限制;线下工具证据更深,却只覆盖少量可复现场景;Benchmark 擅长做改动前后对比,却不能代表线上分布;平台信号的口径稳定性较好,也要服从版本、设备支持和数据可见性限制。

Android vitals 与自建 APM 的数值不应强求一致。Play 数据只覆盖认证设备、从 Google Play 安装且同意共享数据的用户;其问题率按 DAU(Daily Active Users,日活跃用户)计算,第三方 SDK 常按 session(使用会话)、启动次数或采样事件计算。合并看板前要把人群、时间窗口、分母和去重规则写清楚。

四类证据各有用途

指标:发现趋势与影响面

指标是聚合结果,例如:

  • cold_start_p50_mscold_start_p95_ms
  • slow_frame_ratefrozen_frame_rate
  • user_perceived_anr_ratecrash_user_rate
  • lm_kill_user_ratenative_crash_rate
  • request_ttfb_p95_msrequest_failure_rate

指标适合版本门禁、趋势和分组排序。它不能指向某一行代码;P95 上升只说明分布尾部变差,还需要样本和上下文。

这些名称保留英文,便于和看板字段对应:p50 是中位数,p95 / p99 是第 95 / 99 百分位,ms 是毫秒,rate 是发生率;TTFB(Time to First Byte)表示从发起请求到收到首字节的时间。这里的版本门禁指达到阈值后阻止继续发布。

样本:保留一次现场

样本是一次事件,例如:

  • 慢帧的 UI state、frame duration 与主线程堆栈;
  • ANR 的线程堆栈、reason、前后台和近期操作;
  • Java/native crash 的符号化栈(把地址还原为函数和源码位置)与 Build-ID(构建产物标识);
  • OOM/LMK 附近的 PSS(按共享比例折算的内存)、RSS(驻留物理内存)、heap 摘要和页面;
  • 网络请求的 DNS 解析、connect(建连)、TLS 握手和 TTFB 分段。

样本数不能直接当发生率。异常触发采样、设备离线、磁盘满、进程死亡和上传限流都会改变样本被看见的概率。

Trace / profile:还原时间与资源关系

trace、CPU sample(定期截取调用栈)、heap dump(堆对象快照)和 GPU capture(GPU 命令与资源快照)分别用于还原时间线、CPU、内存与图形现场:

  • Perfetto 解释线程调度、Binder、I/O、渲染和系统服务;
  • simpleperf 解释 CPU 耗时集中位置与 native 调用栈;
  • Hprof(Java 堆转储格式)、heapprofd(native 堆分析器)或 LeakCanary 解释对象、分配和引用关系;
  • AGI 解释 GPU 命令、shader(着色器程序)、资源和渲染 pipeline(流水线);
  • ProfilingManager 在受支持版本上提供受控的 system trace、heap dump、heap profile 或 stack sample。

这类文件较大、采集成本高,不适合把每个事件都上传。常见策略是先按轻量指标筛选,再对少量样本提升证据等级。

上下文:决定样本能否比较

最小上下文应覆盖:

  • App version、version code、build variant(构建变体)、发布渠道;
  • ProGuard/R8 Mapping ID(混淆映射表的版本标识)、native ELF Build-ID;
  • Android 版本、build fingerprint(系统构建标识)、机型、ABI(Application Binary Interface,原生二进制接口)、RAM 容量档位;
  • 进程、页面/场景、前后台、刷新率、实验组;
  • event_idsession_idtrace_id 与用户匿名标识;
  • 采样概率、SDK 版本、采集配置版本。

上下文字段若随意改名或高基数失控,同一个问题会被拆散,服务端存储和查询成本也会膨胀。高基数指字段存在大量不同取值,例如未归一化的 URL 或文件路径。

常见采集路线的工程代价

采集方式可见范围适合的问题主要边界
Looper logging / message observer主线程消息的执行区间长消息、主线程阻塞、ANR 前堆栈看不到消息之外的 RenderThread/GPU/系统等待;观察者自身会占主线程
Choreographer(帧调度器)回调App UI frame 节奏连续帧间隔、动画/滚动场景帧间隔不等于完整呈现耗时,刷新率变化也会改变阈值
FrameMetrics / JankStatsWindow frame 与 UI state慢帧率、场景归因API 版本决定字段;回调必须快速返回
字节码插桩被插桩方法的调用与耗时启动节点、热点路径、业务 trace增加构建复杂度与运行代码;R8(代码压缩与混淆器)、AGP(Android Gradle Plugin)和 Kotlin 版本要回归
PLT/inline hook、JVMTI、malloc hooknative/Java 运行时事件I/O、分配、线程、函数调用ABI、linker、符号、ROM 与安全策略带来兼容成本
系统 profiling调度、系统服务、heap、stack 等少量高价值现场受权限、频率限制、redaction(敏感字段裁剪)和系统支持约束
外部测试采样FPS、CPU、内存、温度、功耗等QA、竞品或设备对比不等于线上用户数据;采样源和指标定义要核对

PLT/inline hook 会改写 native 函数跳转,JVMTI(Java Virtual Machine Tool Interface)提供 Java 虚拟机观测接口,malloc hook 观察 native 内存分配。它们能看到更底层的事件,也更依赖 ABI、动态链接器(linker)和设备 ROM(厂商系统)实现。

采集代码也会改变被测系统。主线程抓栈、每帧序列化、Hprof dump、native allocation tracking(原生内存分配跟踪)和长 trace 都可能造成额外卡顿、内存或 I/O。上线前要在高端与低端设备上测量 CPU 时间、主线程时间、内存、包体、磁盘、流量和电量,不能只测“功能能否收到数据”。

JankStats 与 FrameMetrics 的准确边界

截至 2026-08-14,androidx.metrics:metrics-performance:1.0.0 是稳定版本。JankStats 在 API 24+ 基于 FrameMetrics,在更早版本使用 OnPreDrawListener;使用同一套 JankStats API,不表示各系统版本能提供相同精度。

版本差异可按下面理解:

系统版本主要来源能力边界
API 23 及以下OnPreDrawListener在主线程回调,估计 UI frame 时长,无法获得现代 FrameMetrics 字段
API 24—30Window.OnFrameMetricsAvailableListener可获得 UI/CPU 相关耗时;回调由 FrameMetrics 线程交付
API 31—37FrameMetrics + deadline(帧截止时间)信息可使用 frameOverrunNanos / DEADLINE 判断超过目标帧期限的时长

JankStats listener 每帧都会收到数据,必须快速返回。FrameData 会被复用,回调返回后若还要异步处理,应复制需要的值,不能缓存原对象引用。

FrameMetrics 的 TOTAL_DURATION 表示该帧从开始到提交给显示子系统的总时长;各阶段(stage)可能重叠,所以子项之和不必等于 total。它也不等于物理屏幕真正显示(present)的时间。需要分析 SurfaceFlinger/HWC(Hardware Composer,显示硬件合成器)和 display present 时,应转向 Perfetto FrameTimeline、SurfaceFlinger 和 GPU/display 证据。

JankStats 的 UI state 需要由应用维护。页面、滚动、过渡、列表类型等状态没有及时清理时,慢帧会被关联到过期场景,服务端统计会很精确地计算出一个错误结论。

ApplicationExitInfo:退出记录不是 OOM 结论

应用 API 与 Android 17 服务端路径

API 30 起,应用可调用 ActivityManager.getHistoricalProcessExitReasons() 查询历史 ApplicationExitInfo。Android 17 的调用路径是:

App
  → ActivityManager.getHistoricalProcessExitReasons()
  → Binder / ActivityManagerService
  → ProcessList.mAppExitInfoTracker
  → per-package / per-UID exit records
  → NativeTombstoneManager 合并可访问 tombstone

UID 是 Android 基于 Linux 用户标识实现的应用隔离身份。上面的路径说明查询会跨 Binder 进入 system_server(承载核心 Android 服务的进程),还可能合并 tombstone(native 崩溃记录)。它不应阻塞冷启动首帧;可通过后台 executor(执行线程池)查询最近记录,按 timestamp、process name 和 version/session 边界去重后再上报。

AppExitInfoTracker 在 Android 17 中是独立顶层类,实例由 ProcessList 字段 mAppExitInfoTracker 创建并初始化。AOSP 基础配置为每个 package/UID 最多保留 16 条记录,设备厂商可通过资源 overlay(覆盖系统资源配置)改变容量。记录通过 AtomicFile 写入 procexitstore/procexitinfo;这种写法可在失败时回滚,避免留下半份文件。非紧急更新按 30 分钟间隔调度写入,package/user 移除时会清理对应记录。

reason 来自多路事实合并

进程死亡与 ActivityManagerService(AMS)主动终止形成基础记录;lmkd(低内存管理守护进程)和 zygote(应用进程孵化器)的外部通知可以补充 status、RSS 或修正 reason。Android 17 源码还保护已经明确为 ANR、Java crash 或 native crash 的记录,避免后到的模糊信号覆盖高价值原因。

常见公开 reason 包括:

reason解释时要保留的边界
REASON_SIGNALED2结合 getStatus() 的 signal(操作系统信号);在不支持 LMK 上报的设备上,内存压力终止可能表现为 SIGKILL
REASON_LOW_MEMORY3先检查 ActivityManager.isLowMemoryKillReportSupported();该 reason 本身不提供 heap dump 或 Java OOM 证据
REASON_CRASH4Java 未处理异常,不等于 native crash
REASON_CRASH_NATIVE5API 31+ 可能附带 protobuf 格式的 tombstone stream(结构化 native 崩溃数据流),也可能因覆盖而为空
REASON_ANR6通常可取得系统保存的 ANR trace,但不保证永远存在
REASON_USER_REQUESTED10包含 force-stop、最近任务移除等用户相关路径,需结合 description 和当时场景
REASON_OTHER13依赖 description 与上下文,不能直接归为应用缺陷
REASON_FREEZER14API 33+ 的 freezer(缓存进程冻结机制)相关退出
REASON_PACKAGE_STATE_CHANGE / UPDATED15 / 16API 34+ 区分组件状态变化与包更新

getPss()getRss() 是系统上一次采样值,可能为 0,也不是死亡瞬间内存。getTraceInputStream() 通常用于 ANR;API 31+ 还可返回 native tombstone protobuf。进程曾发生并恢复的 ANR,其 trace 也可能附在之后因其他 reason 死亡的记录上。trace 和 tombstone 由独立循环存储管理,可能被更新事件覆盖,因此返回 null 是合法结果。

下面的代码用于展示一次低优先级、可去重的历史退出查询;生产代码还要接入自己的持久化和上传队列。

executor.execute {
    val records = activityManager.getHistoricalProcessExitReasons(
        null, // 仅查询调用方 UID 可访问的记录
        0,    // 不按 PID 过滤
        16,
    )

    records.forEach { info ->
        reportExitIfNew(
            processName = info.processName,
            timestampMs = info.timestamp,
            reason = info.reason,
            status = info.status,
            pssKb = info.pss,
            rssKb = info.rss,
        )
    }
}

packageName = null 时,服务端按调用方 UID 过滤;应用不能借此读取任意包的历史。timestamp + processName + reason/status 只能作为去重基础,跨 reinstall(重装)、数据清除和时钟变化时还要加入 install/build/session 标识。

ProfilingManager:请求可能被限频或拒绝

API 35 引入 android.os.ProfilingManager,支持 Java heap dump(Java 堆转储)、heap profile(堆分配采样)、stack sampling(调用栈采样)和 system trace(系统时间线)请求。请求会受到频率限制,也不保证执行;结果会裁剪敏感和无权限数据,只包含请求进程可访问的信息。

API 36 增加 ProfilingTrigger,应用可注册 ANR、reportFullyDrawn() 等系统触发事件。API 37 又增加 OOM、冷启动、应用兼容性异常和过量 CPU 使用等类型;不同 trigger 返回的文件不同,例如 OOM 返回 Java heap dump,冷启动返回 system trace 和 stack sampling。trigger 同样不保证产生结果,调用方需要注册全局 result listener(接收该 UID 全部结果的回调),处理应用重启后的重新交付,并为文件过期、上传和去重设计流程。

Android 17 上可以把 ProfilingManager 接入少量高价值样本,例如:

  • 启动 P99 异常且设备满足采集条件;
  • 特定页面连续慢帧,并已通过轻量信号筛选;
  • 远程配置选择的低比例实验人群;
  • 系统 trigger 返回的 ANR、OOM 或冷启动 profile。

不要对每次慢帧请求 system trace,也不要把 callback(回调)未返回直接解释成 API 故障。频率限制、资源状态、系统策略和进程生命周期都可能让请求没有结果。

什么时候 APM 证据不够

线上现象APM 能提供的入口后续验证工具
主线程长任务message duration、主线程 stack、页面Perfetto thread state、method trace、CPU profiler
CPU 抢占或频率受限慢帧/启动样本、设备与温度上下文Perfetto scheduler/cpufreq、simpleperf、thermal 信息
Binder 卡住调用点 stack、超时样本Perfetto binder transaction、对端线程与服务 trace
I/O 等待文件类别、耗时、调用 stackPerfetto/ftrace I/O、simpleperf;必要时检查文件系统与内核事件
GPU/显示晚frame duration、场景、Surface 类型Perfetto GPU/FrameTimeline、AGI、SurfaceFlinger/HWC
Java 对象泄漏heap 使用趋势、页面、退出原因Hprof、LeakCanary、Profiler
native 内存增长RSS/PSS 趋势、native stack sampleheapprofd、malloc debug、KOOM/厂商工具
LMK/OOMexit reason、PSS/RSS、设备 RAM、前后台ApplicationExitInfo、lmkd/PSI trace、heap/native memory 分解
网络慢DNS/connect/TLS/TTFB 分段与 endpoint 类别客户端网络 trace、服务端 trace、网络环境复现

表中的 thread state 是线程的运行、就绪、休眠或等待状态,binder transaction 是一次 Binder 跨进程请求,cpufreq 是 CPU 频率轨道,thermal 指温度与降频信息。PSI(Pressure Stall Information)描述 CPU、内存或 I/O 压力造成的停顿,endpoint 指网络请求目标,method trace 记录方法调用时间线,ftrace 是 Linux 内核跟踪机制。

普通应用看不到完整系统和内核现场。需要 scheduler、driver、dma-fence、lmkd/PSI 或系统服务内部细节时,要在可控测试设备上采集 Perfetto/ftrace,或与平台/OEM(设备制造商)团队协作。缺失字段应明确标为未知,不能补成推测。

线上采样要先写清数据合同

数据合同要先于 SDK 接入。它规定“客户端采什么、服务端怎样解释、多久删除、如何关联构建”,避免同名字段在不同版本中表达不同含义。

下面的 JSON 只展示最小结构,用于讨论字段职责,不代表特定后端协议。

{
  "schema_version": 3,
  "event_name": "jank_frame_sample",
  "event_id": "01J...ULID",
  "occurred_at_epoch_ms": 1784908800123,
  "duration_ns": 42800000,
  "sample_probability": 0.02,
  "session_id": "anonymous-session-id",
  "trace_id": null,
  "app": {
    "version_name": "8.4.0",
    "version_code": 804000,
    "mapping_id": "r8-mapping-uuid",
    "native_build_ids": ["7f3a..."]
  },
  "device": {
    "sdk_int": 37,
    "build_fingerprint_hash": "sha256:...",
    "model_class": "mid_ram_6g",
    "abi": "arm64-v8a"
  },
  "context": {
    "process": "main",
    "screen": "image_editor",
    "interaction": "pinch_zoom",
    "foreground": true
  }
}

event_id 示例使用 ULID(可按时间排序的唯一标识),schema_version 表示事件结构版本。耗时字段要在名称或 schema 中固定单位;sample_probability 是该事件被采中的概率,用于加权估计,不能丢失;Mapping ID 与 Build-ID 用于还原 Java/Kotlin 和 native 栈。原始机型、URL、文件路径和用户信息取值多、识别风险高,应按数据最小化原则处理。

一份可执行的数据合同至少定义:

  • 事件名、schema version、字段类型、单位和 nullable(是否允许空值)规则;
  • 指标窗口、分母、去重、分位数算法和时区;
  • 用户/session/event/trace 的关联与生命周期;
  • Java Mapping ID、native Build-ID 和 source revision(对应的源码版本);
  • 采样单位是用户、session、事件还是异常;
  • URL、请求头、日志、路径、截图和标识符的脱敏规则;
  • 本地保留、上传重试、服务端保留和删除周期;
  • SDK 配置版本、远程开关与回滚方式。

端侧采样、缓存与上传

采样

不同事件使用不同策略:

  • crash、ANR 等低频高价值事件优先保留完整轻量元数据;
  • 每帧、每请求等高频数据先在端侧聚合;
  • trace、heap dump 等重文件只对少量候选样本采集;
  • 用户/session 采样尽量使用稳定 hash(相同输入得到相同分组),避免每次启动换一批人,导致同一批用户的前后变化难以比较;
  • 服务端展示发生率时校正采样概率和上传成功率。

“异常才采样”会产生选择偏差。例如只在设备空闲、充电且网络良好时上传 trace,样本天然偏向特定设备状态;结论中要保留这项限制。

本地缓冲

缓冲区应有总字节、单事件、单文件、条数和年龄上限。写入使用临时文件与原子 rename(重命名要么完整成功,要么保持原状),或使用具备事务语义的存储(整组操作全部成功或全部回滚),避免进程被杀后留下半文件。重文件与普通指标分目录管理,过期和低价值事件优先淘汰。

敏感数据应在写盘前完成裁剪或脱敏。不能先保存原始 URL、token(认证凭据)、日志和用户数据,再指望上传阶段处理;进程死亡后,这些原始文件仍可能留在磁盘。

上传

上传需要批量、压缩、指数退避(失败越多,重试间隔越长)、抖动(给重试时间加入随机偏移)、幂等 event ID(重复上传仍识别为同一事件)和服务端去重。网络、充电、温度、前后台等条件应按文件价值设置,不能让 APM 自身制造启动竞争、流量尖峰或发热。

服务端收到事件不代表数据完整。客户端要上报丢弃原因计数,例如 quota(配额耗尽)、serialization error(序列化失败)、disk full(磁盘已满)、expired(已过期)、rate limited(触发频率限制)和 upload failed(上传失败);否则看板只描述“成功上传的人群”。

选型:从团队约束推导组合

选型之前先区分“构建工具能解析依赖”和“可以进入生产环境”。一个 APM artifact(发布的库产物)要通过 Android 17 验证,至少要检查构建、打包、启动、采集、失败降级、停用恢复与隐私七层;仓库 README、较低的 minSdk(最低支持 Android 版本)、一次启动成功或 AOSP 中仍存在同名私有字段,都不能替代这些证据。

用成对变体测量 APM 自身开销

同一 commit(源码版本)至少准备以下 release 变体,保持 R8、应用签名、ABI、资源和业务配置一致:

变体内容用途
baseline不包含被测 SDK建立设备与业务基线
linked-off包含依赖,但模块全部关闭观察打包、类加载和初始化前影响
module-on一次只开启一个模块将成本归属到具体采集器
production-set生产计划中的模块、采样与上传配置验证组合效应
event-trigger受控制造主线程阻塞、内存 dump、泄漏或上传失败测量峰值和失败恢复

测试至少覆盖冷启动、前台空闲、固定交互、高频 Looper Message、后台静置和受控异常;轮次按 baseline/variant(基线/被测变体)交替或随机排列,每轮使用相同的温度、电量、刷新率、网络和数据状态。Android 17 还要覆盖 4 KB 环境,以及关闭 16 KB backcompat(向后兼容)模式的 16 KB 环境;后者会让不兼容的 native 二进制立即中止。业务占比高的厂商 ROM 也要单独测试。

CPU 使用 Perfetto 中的进程/线程 running time(实际占用 CPU 的时间),帧使用 FrameTimeline、JankStats 或 FrameMetrics,内存同时记录 PSS、RSS、Java/native heap 和峰值,启动由 Macrobenchmark 固定测试模式。I/O、网络、唤醒、包体、事件丢失、关闭后残留线程和异常恢复也要进入结果。不能从源码操作数估算“低于 0.1%”,也不能把周期 tracker(定时采集器)与一次 heap dump 汇总成同一个平均开销。

发布表同时给绝对差值、相对差值、中位数、尾部值、最差有效轮和样本数。没有实测的数据保留为空;性能差但流程完整的轮次不能按异常值删除。最终 APK/AAB(Android 安装包/应用包)中的所有 native 依赖,还要分别检查 ELF(native 可执行文件格式)的 LOAD segment(可加载段)对齐、ZIP 内文件对齐和真机 page size(内存页大小);三项检查回答的问题不同。

Android 17 接入准入清单

清单中的 NDK 是 Native Development Kit(原生开发工具包),targetSdk 是应用声明适配的目标 API 版本。

  • 固定仓库、commit、依赖坐标、AGP、NDK、R8、targetSdk 与配置版本。
  • clean build(全量构建)、增量构建、Gradle configuration cache(配置缓存)、各 variant(构建变体)和最终安装产物均通过。
  • manifest、权限、Provider、Service、通知、PendingIntent、文件目录和网络安全配置符合当前 Android 的组件声明、权限和后台执行要求。
  • 4 KB/16 KB 设备验证安装、初始化、正常事件、边界事件、失败事件和关闭模块。
  • 反射、私有符号、Hook、Printer(Looper 日志回调)、子进程、磁盘和上传失败都会显式降级,不以“没有报告”冒充“没有问题”。
  • 关闭模块后恢复 Hook/Printer,停止线程与任务,缓存满足大小、年龄、重试和删除限制。
  • 报告保存原始 trace、脚本 commit、测试顺序、判废原因和 SDK 自监控数据。

具体工具的构建和运行边界留在各自正文:Matrix 看 17.2,KOOM 看 17.3,BlockCanary 与历史开源项目看 17.5。这样兼容性结论只维护一次,通用实验协议也不再单独占用一个重复编号。

团队条件优先能力原因
没有线上性能平台Android vitals + crash/ANR + 基础启动/页面指标建立稳定趋势和版本分组,再补专项
UI 卡顿是主问题JankStats + UI state + 少量 Perfetto/Macrobenchmark轻量覆盖真实场景,深证据用于归因
Java/native 内存问题多ApplicationExitInfo + heap/native 专项工具区分 crash、LMK、ANR,再选择 Hprof/heapprofd/KOOM
已有日志与数据平台自研轻量 SDK 或开源采集组件可复用认证授权、上传、查询和告警,但仍需评估客户端维护成本
发版节奏快、AGP/Kotlin 升级频繁少 hook、少插桩,优先稳定系统/Jetpack API降低构建链和运行时兼容风险
低端设备占比高更低采样、更小缓冲、端侧聚合、严格 kill switch(远程紧急关闭开关)APM 开销更容易污染被测性能
隐私或合规限制严格数据最小化、端侧聚合、短保留期、可审计 schema降低原始内容离开设备的范围

Matrix、KOOM、btrace/RheaTrace、Measure、Firebase、Sentry、APMPlus 等工具各自覆盖一部分问题。项目活跃度、license(许可证)、版本兼容和维护者状态会变化,接入前要查看对应仓库、release(发布版本)和最小验证应用,不能仅凭过时的“主流/维护中”标签判断。

分阶段建设顺序

  1. 统一 build、version、device、process、screen、session 和采样字段。
  2. 接入 Android vitals、crash/ANR 与基础启动/页面指标,建立版本门禁。
  3. 按主要问题加入 JankStats、ApplicationExitInfo、网络分段或内存信号。
  4. 从轻量事件中筛选少量样本,再请求 trace、profile、heap 或转到实验室复现。
  5. 把 Macrobenchmark/Microbenchmark 和关键场景回归接入 CI。
  6. 增加远程开关、采集预算、丢弃统计、隐私审计和 SDK 自身性能监控。

每一步都应能单独关闭和回滚。没有远程 kill switch 的 hook、每帧监听或重文件采集,不适合直接进入大规模 Release。

后续章节怎样分工

  • 17.2—17.4:看 Matrix、btrace、Tracing SDK、KOOM、LeakCanary、DoKit 与 Measure 的采集和工程边界;
  • 17.5:理解 BlockCanary、ArgusAPM 等历史方案能保留的设计和必须替换的实现;
  • 22.10:看 JankStats、FrameMetrics、系统合成器分类与线上卡顿治理;
  • 17.6—17.7:看 Jetpack Benchmark、Baseline Profile 验证、外部性能测试与设备 benchmark;
  • 17.8—17.12:看网络、crash/ANR、功耗、WebView/Flutter 混合栈与大规模端侧架构。

阅读某个工具前,先回答它处在“线上采集、系统信号、线下诊断、回归测量”中的哪一层。这样能避免用一款工具负责它没有数据权限或没有证据深度的问题。

源码与文档入口

常见误判

误判修正
接入一个 SDK 就有完整 APMSDK 只是采集端;还需要数据合同、缓存上传、符号化、聚合、查询、告警和验证
Android vitals 与自建 crash rate 应完全相等人群、分母、去重、窗口和隐私阈值不同
JankStats 报 jank 就说明主线程慢继续检查 CPU 调度、RenderThread、GPU、SurfaceFlinger 和刷新率
FrameMetrics 各阶段之和等于总时长阶段可以并发,API 文档明确不保证相等
REASON_LOW_MEMORY 代表 Java heap OOM它表示系统内存压力 kill;先检查设备是否支持该 reason,再拆 PSS/RSS/heap/native
getTraceInputStream() 对 ANR/native crash 必有数据独立循环存储可能覆盖,ANR 恢复后也可能把 trace 带到其他 reason
ProfilingManager 请求一定返回请求受频率限制和系统策略控制,不保证执行
样本数就是发生率采样、进程死亡、磁盘、网络和服务端去重都会影响可见样本
外部 benchmark 分数代表线上体验分数只在相同版本、设备状态和测试条件下可比较
APM 没采到 scheduler/GPU 细节,就能用堆栈推断缺系统证据时转向 Perfetto/AGI/OEM 工具,并明确未知项

Firebase Performance 的自动与自定义 Trace

明确 APM 通用能力后,可以按 Firebase 的自动启动、网络和屏幕信号检查其覆盖与限制。

Firebase Performance 的定位

Firebase Performance Monitoring 是 Firebase 提供的托管型性能监控服务:数据接收、存储和控制台由 Firebase 运营,团队只需在应用中接入 SDK,无需自行部署采集服务器。它会采集启动、前后台、屏幕渲染和部分 HTTP/S 请求,也允许应用补充业务 trace。控制台按版本、设备、国家或地区等维度聚合数据。

它适合中小团队快速建立基础性能看板(汇总关键指标的 dashboard),也可以补充成熟监控体系,用来观察各版本的长期变化。它不提供自托管采集服务,端侧还会采样和限流,控制台展示也达不到秒级。因此,Firebase Performance 无法单独承担实时故障发现、还原单次请求的完整过程、解释每一帧为何变慢,或诊断 native(C/C++ 等本地代码)问题。

截至 2026 年 8 月 14 日,当前 Firebase 构建版本如下:

组件版本说明
Firebase Android BoM34.17.0统一 Firebase Android 库版本
firebase-perf22.0.6BoM 对应的 Performance SDK
Performance Gradle plugin2.0.2网络请求和 @AddTrace 字节码插桩
Google services plugin4.5.0处理 google-services.json

BoM(Bill of Materials)用于让一组 Firebase 库采用彼此兼容的版本。firebase-perf:22.0.6 的 AAR(Android Archive 库包)声明最低支持 API 23;本文内容按 Android 8(API 26)到 Android 17(API 37)复核。Firebase Android BoM 从 34.0.0 起不再包含独立 KTX module(Kotlin 扩展构件),这些扩展 API 已并入主 module,依赖仍写 firebase-perf

数据模型:trace、metric、attribute

Firebase Performance 用 trace 表示一段被计时的执行区间,用 metric 表示数值指标,用 attribute 表示便于筛选的键值标签。自动 trace 和 custom trace 都带有 duration 等内建 metric;custom trace 还能记录自定义 metric 与 attribute。Network request trace 还会保存 URL pattern(把相似 URL 归为一组的匹配规则)、HTTP method、status code、payload size 和 Content-Type 等网络字段。

对象含义例子使用建议
trace一段被计时的执行区间_app_starthome_first_feed名称固定,不拼动态值
metrictrace 中的整数计数或内建耗时duration、item_countretry_count用数值表达次数或数量
attribute用于过滤和分组的键值标签entry=cold_startresult=success只放可选值少且固定的枚举,不放 user id
network request trace一次被捕获的 HTTP/S 请求GET api.example.com/v1/items/**用 URL pattern 聚合动态路径

metric 适合记录条目数、重试次数等整数;trace duration 由 start()stop() 自动计算。attribute 用于筛选和分组。用户 id、订单号、完整搜索词等字段可能产生大量不同取值,这类高基数字段会把样本切成许多小组;它们还可能属于 PII(Personally Identifiable Information,可识别个人的信息),不应交给 Performance Monitoring。

这套模型擅长回答“哪个版本变慢”“哪类设备更慢”“哪条业务路径分布异常”,但不能还原一次故障的完整调用栈。

构建接入:插件与运行库分工

Performance Gradle plugin 与运行时 SDK 负责不同工作。这里的字节码插桩,是指插件在构建时修改编译产物,自动插入计时和采集调用:

  • com.google.firebase.firebase-perf 在构建期对受支持的网络库和 @AddTrace 做字节码插桩。
  • firebase-perf 在进程中记录、采样、暂存并上传性能事件。
  • com.google.gms.google-services 读取 google-services.json,把 Firebase 项目配置转换成 Android resource(应用资源)。

下面的 Kotlin DSL 示例保留了 2026 年 7 月可用的 BoM 34.16.0。新接入或本次升级时,应把示例中的这一项改成当前版本 34.17.0;两版 BoM 都会选择 firebase-perf:22.0.6,另外两个 Gradle plugin 版本不变:

plugins {
    id("com.android.application") version "<项目使用的 AGP 版本>" apply false
    id("com.google.gms.google-services") version "4.5.0" apply false
    id("com.google.firebase.firebase-perf") version "2.0.2" apply false
}

// app/build.gradle.kts
plugins {
    id("com.android.application")
    id("com.google.gms.google-services")
    id("com.google.firebase.firebase-perf")
}

dependencies {
    implementation(platform("com.google.firebase:firebase-bom:34.16.0"))
    implementation("com.google.firebase:firebase-perf")
}

代码中的 AGP 指 Android Gradle Plugin,其版本沿用项目现有配置。BoM 只管理 Firebase 库版本,不管理 Gradle plugin 版本,因此两个 plugin 的版本仍要单独声明。使用 version catalog(版本目录)或根构建脚本时,可以采用等价写法。

构建插桩和数据采集不是同一个开关

团队经常只关闭运行时采集,却仍让 debug 构建执行字节码插桩。构建期和运行期是两套开关,需要分开配置:

控制项作用时机结果
FirebasePerfExtension.setInstrumentationEnabled(false)指定 variant 的构建期不对该 variant(如 debug、release)执行自动网络和 @AddTrace 插桩
Gradle property firebasePerformanceInstrumentationEnabled=false整次构建全局关闭 Performance 插桩;适合 CI(持续集成)参数或临时诊断
firebase_performance_collection_enabled=false应用运行期默认不采集;之后可由 setPerformanceCollectionEnabled(true) 改变
firebase_performance_collection_deactivated=true应用运行期强制停用并覆盖 enabled;只有删除该 Manifest 项并重新发版才能恢复

下例通过 Android Manifest 元数据把运行时采集默认关闭,待用户同意隐私政策,或分阶段启用策略选中该设备后再开启:

<application>
    <meta-data
        android:name="firebase_performance_collection_enabled"
        android:value="false" />
</application>

这段 Manifest 配置不会自动关闭构建插桩。若 debug variant 不需要插桩,还要在构建配置中调用 FirebasePerfExtension.setInstrumentationEnabled(false);若只想停止采集和上传,则无需关闭插桩。

Firebase 没有“debug 构建默认关闭”的通用规则。可采用这样的发布策略:debug variant 明确关闭插桩与采集,内部测试包只对少量受控设备启用,release 再按产品政策开放。接入清单还应覆盖 Firebase IAM(Identity and Access Management,身份与权限管理)、BigQuery 数据仓库权限、服务可用地区、数据保留说明、用户同意机制和隐私政策。Google Analytics 权限不属于 Performance Monitoring 的基本接入条件。

多进程应用要特别留意:官方只支持主进程中的 Performance Monitoring。独立 :remote:push 等远程进程不会自动获得与主进程相同的采集能力;需要观察这些进程时,仍要保留应用自己的多进程监控。

自动采集能力与版本边界

自动采集覆盖的是 SDK 明确识别的生命周期和网络调用,不等于应用发生的每一个性能事件。

能力采集方式稳妥边界局限
App start自动记录 Firebase SDK 定义的启动区间不是进程 fork 到首帧,也不是完整首屏
Foreground / background自动依据进程生命周期记录会话区间不等同于业务页面停留
Screen rendering自动Activity;SDK 20.1.0+ 增加 Fragment固定 60 Hz 阈值,缺少逐帧业务状态
HTTP/S request自动 + 手工自动覆盖受支持的 Java/Kotlin 网络库调用Cronet、native 或自研栈需要手工记录
Custom trace手工或 @AddTrace业务阶段、解码、查询等代码区间需要设计稳定字段,@AddTrace 不能附加自定义数据

Android 17 / API 37 没有一套单独的 Firebase Performance 统计规则。这里以 SDK 22.0.6 的官方文档和源码为准;跨版本比较时,也要记录 SDK 版本和采样策略是否改变,避免把采集方式的变化误判为应用性能变化。

App start:不要把 _app_start 当成完整启动

firebase-perf:22.0.6 中,SDK 内部把这条 trace 记为 _as,控制台显示为 _app_start。计时使用单调时钟 elapsedRealtime:它按设备启动后的经过时间递增,不受用户修改时间或网络校时影响。它的区间是:

  1. 起点:Firebase 首个类的早期 class-load(类加载)近似时间。
  2. 终点:第一个 Activity 的 onResume() 回调时间。
  3. 子区间:到首次 onCreate()onStart()onResume() 的几个阶段耗时。

这个区间没有覆盖 Firebase 初始化之前的全部进程时间,也没有等待第一帧或首屏内容可用。API 24 以后,源码会读取 Process.getStartElapsedRealtime(),但该时间用于实验性 TTID trace 及 process start → class load 子区间,不能把它写成稳定 _app_start 的起点。这里的 process start 接近系统从 Zygote fork(派生)应用进程的时刻;Process.getStartUptimeMillis() 也不是稳定 _app_start 使用的时钟。

SDK 会过滤后台触发的进程启动。22.0.6 修复了 Android 14(API 34)及以上版本的判断:在 Firebase 的早期初始化阶段调用 ActivityManager.getMyMemoryState(),只有 IMPORTANCE_FOREGROUND 才允许生成 _app_start。这个变化同样覆盖 Android 17。

_app_start 可用于比较版本趋势,不能替代应用定义的 TTID(Time to Initial Display,首次画面出现时间)和 TTFD(Time to Full Display,完整内容可用时间)。若“启动完成”要求首页骨架绘制、首批数据展示或页面可交互,需要另建 custom trace,并用 Macrobenchmark(Jetpack 的端侧性能基准工具)或 Perfetto 系统 trace 校验各阶段。

Screen rendering:指标是“屏幕实例比例”

Firebase 自动 screen trace 的时间窗口取决于页面类型:

  • Activity:onActivityStarted()onActivityStopped()
  • Fragment:onFragmentResumed()onFragmentPaused(),需要 SDK 20.1.0+

Fragment 不会独立读取一条 FrameMetrics 流。FrameMetrics 是 Android 提供的 Window 帧耗时数据;SDK 从宿主 Activity 的 Window 收集它,再计算 Fragment 生命周期区间内的计数差值。多个 Fragment 重叠、生命周期管理不规范,或使用单 Activity 的 Compose Navigation,都会让页面级结果更难解释。

SDK 22.0.6 对单帧的分类仍是:

  • slow frame:耗时 > 16 ms
  • frozen frame:耗时 > 700 ms

官方文档明确指出,自动 screen rendering trace 按固定 60 Hz 阈值计算。在 60 Hz 屏幕上,每个刷新周期约为 16.67 ms;在 90/120 Hz 设备上,刷新周期缩短为约 11.11/8.33 ms。一帧即使错过设备的真实刷新周期,也可能没有超过 16 ms,因此 Firebase 可能低估高刷新率设备上的卡顿。

控制台中的汇总百分比也不是“慢帧数除以总帧数”:

  • slow rendering:slow frame 超过该 screen instance 总帧数 50% 的 screen instance 占比;
  • frozen frames:frozen frame 超过该 screen instance 总帧数 0.1% 的 screen instance 占比。

这里的 screen instance 指一次 Activity 或 Fragment 展示区间,不是一帧。自动 screen trace 不能附加 custom metric 或 custom attribute。单 Activity + Compose 应用若要区分具体 route(导航目的地)、滚动阶段或业务动作,应使用 JankStats(Jetpack 的逐帧卡顿统计库)记录状态,并按需增加 custom trace。

自定义 trace:字段规则与服务端边界

custom trace 的命名要保持长期稳定,也要符合控制台和后端的公开约束:

项目公开约束工程建议
trace name最长 100 个字符;无首尾空格;不能以 _ 开头使用固定 snake_case(小写单词以下划线连接)
metric name最长 100 个字符;无首尾空格;不能以 _ 开头表达可累加的整数计数
trace metrics每条 custom trace 最多 32 个,duration 也计入只保留诊断需要的少量指标
attribute每条 custom trace 最多 5 个采用取值少且固定的枚举
attribute key文档口径最长 32 个字符,只使用英文字母与 _遵循文档口径,不依赖 SDK 的宽松校验
attribute value最长 100 个字符不写 PII、认证 token 或自由文本

这里有一处容易误读的源码差异:SDK 22.0.6 的端侧校验允许 attribute key 最长 40 个字符,也允许首字母后的数字,并拒绝 firebase_google_ga_ 前缀;当前 Android 官方文档给出的服务端规则更严格,只接受最长 32 个字符以及字母、下划线。生产代码应采用两套规则共同接受的范围。通过端侧校验,只能说明本地 SDK 接受了该字段,不能保证服务端和控制台会长期保留它。

场景trace 名metric 名attribute不要写
首屏首批内容home_first_feeditem_countpayload_kbentry=cold_start完整接口 URL、user id
登录流程login_requestretry_countresult=successresult=fail手机号、邮箱
图片解码image_decode_listimage_countdecode_mssource=disksource=network图片 hash(哈希值)、CDN(内容分发网络)签名
数据库查询db_query_userrow_countquery_mssource=roomSQL 原文、主键 id

下面的示例用主 module 中的 Kotlin trace 扩展保证异常路径也会调用 stop()

import com.google.firebase.Firebase
import com.google.firebase.perf.performance
import com.google.firebase.perf.trace

suspend fun loadFirstFeed(): List<FeedItem> {
    return Firebase.performance.newTrace("home_first_feed").trace {
        putAttribute("entry", "cold_start")

        val items = repository.loadFirstPage()
        putMetric("item_count", items.size.toLong())
        items
    }
}

trace {} 内部使用 try/finally 停止 trace,因此抛出异常或协程被取消时也能执行 stop()。若项目不用该扩展,应显式写 start()try/finallystop(),避免 trace 一直没有结束时间。@AddTrace 适合简单方法计时,但它不能附加 custom metric 和 attribute;需要业务字段时,应使用显式 API。

网络请求聚合和 URL pattern

自动 network request trace 记录请求 URL、HTTP method、response code、request/response payload size、Content-Type 和 duration。官方默认把 response code 100..399 计为成功;duration 从发出请求算到完整接收响应。这个总耗时不能继续拆成 DNS(域名解析)、TCP 建连、TLS 加密握手、服务器处理和重试等阶段。

Performance Gradle plugin 2.0.2 的插桩类覆盖以下调用:

  • OkHttp 3.x 的 Call.execute() / enqueue()
  • HttpURLConnection / HttpsURLConnection
  • Apache HttpClient。

官方文档明确列的是 OkHttp 3.x.x。较新 OkHttp 即使保持二进制兼容,也只表示已经编译的调用通常还能找到相同方法,不代表 Performance plugin 一定识别这些调用。升级网络库或插件后,应发送测试请求,并用 SDK debug log 核对是否采集成功。Cronet(基于 Chromium 的网络引擎)、native 网络栈、自研协议栈以及插件未识别的封装,需要手工创建 HttpMetric

手工记录不受支持的网络栈

下面的示例为 Cronet 或自研网络 client 的一次请求创建独立 HttpMetric

import com.google.firebase.Firebase
import com.google.firebase.perf.FirebasePerformance
import com.google.firebase.perf.performance

suspend fun executeWithMetric(request: ApiRequest): ApiResponse {
    val metric = Firebase.performance.newHttpMetric(
        "https://api.example.com/v1/items",
        FirebasePerformance.HttpMethod.GET,
    )
    metric.start()

    return try {
        val response = cronetClient.execute(request)
        metric.setHttpResponseCode(response.code)
        metric.setResponsePayloadSize(response.bodySizeBytes)
        response.contentType?.let(metric::setResponseContentType)
        response
    } finally {
        metric.stop()
    }
}

HttpMetric 不保证线程安全,一次请求应创建一个实例,不要放进 singleton(全局共享的单例对象)反复使用。异常分支也要执行 stop()。若网络 client 能区分 DNS、TLS、timeout 等错误,可在应用自己的少量固定分类字段或错误监控中记录;HttpMetric 没有通用的 network-stage 字段。

URL pattern 要在控制台中统一分组

SDK 在写入 URL 时会移除 user-info(如 user:password@host 中的账号信息)和 query 参数(?key=value),并把长度截到 2000 个字符。这只是上报前的保护措施,凭证仍不应放在 URL 中;自定义 attribute 也不能写 PII、token、签名或自由文本。

控制台会生成 automatic URL pattern,也允许创建 custom URL pattern。这里的 segment 指 URL path 中由 / 分隔的一段;pattern 语法不接受 {id} 这种参数写法:

  • 字面量 segment:api.example.com/v1/items/list
  • *:匹配一个 path segment
  • **:匹配 path 的剩余部分,只能放在 pattern 末尾

例如,/api/item/10001/detail/api/item/10002/detail 应定义为:

api.example.com/api/item/*/detail

这个 pattern 只归并一个动态 segment。若其后还可能出现任意数量的 path segment,才使用末尾 **

同一请求只映射到一个 URL pattern:Firebase 先尝试 custom pattern,再使用 automatic pattern;多个 custom pattern 同时命中时,从 path 左侧开始,按字面量、*** 的顺序选择更具体的一条。新增 pattern 不会追溯改写历史数据,而且新规则最多可能等待 12 小时才出现聚合结果。当前限制是每个 app 最多 400 个 custom pattern、每个 domain 最多 100 个。设计时应按同一类 API 资源归并,不要为每个具体 URL 单独创建 pattern。

payload size 通常依赖 Content-Length 等 HTTP header(请求或响应头)信息;该 header 缺失或填写不准时,控制台数值也可能不准,不能把它当作线路实际传输字节数。缺少 Content-Type 可以被接受,格式非法的 Content-Type 则可能让请求不显示。长时间未完成、没有执行 stop() 或未被插桩命中的请求,也不会形成可用样本。

排查采集时,可在测试构建的 Manifest 临时设置 firebase_performance_logcat_enabled=true,再到 Logcat(Android 设备日志)检查 FirebasePerformance debug log 中是否出现已完成的 trace/request 和对应 URL。验证完成后移除该开关,避免增加生产日志。

采样、时效和排查边界

SDK 记录到事件,不代表控制台会保存设备上发生的每个事件。采样会按一定比例选择设备或事件,限流则限制一个时间窗口内可发送或接收的数量;两者都可能减少最终样本。设备侧还会批量发送,服务端也要继续处理。

SDK 情况控制台时效适合做什么
Android SDK 19.0.10+ 或 BoM 26.1.0+SDK 大约每 30 秒批量发送;控制台通常几分钟内出现分阶段发布观察、当日回归、版本趋势
旧 SDK大约 36 小时延迟次日复盘、长期趋势

firebase-perf:22.0.6 已进入 near real-time(近实时)处理路径,这里的含义是数据通常在采集后几分钟内显示。“几分钟”不能当作严格的实时 SLA(Service Level Agreement,服务时效约定);设备离线、省电策略、初始化失败、上传失败或服务端处理都可能继续推迟数据。

官方 troubleshooting 文档给出的设备限流口径是:code trace 和 network request trace 合计每台设备每 10 分钟最多发送 300 个事件。此外,SDK 会通过 Firebase Remote Config(远程配置服务)取得针对该 app 的动态采样率,随机选择哪些设备发送 trace;服务端仍可能丢弃一部分已收到的事件。启用 BigQuery 集成的项目会获得较高的 network request trace 数量上限,但仍不等于全量采集。因此:

  • 高频轮询和图片请求不会保证逐条保留;
  • 小流量分阶段发布可能因为样本不足而看不出变化;
  • 控制台分布只能代表被捕获并被接受的事件。

Performance alerts 也有样本门槛。App start、custom trace、network 和 screen rendering 告警需要过去一小时至少 100 个样本;这里的样本是 Firebase 已记录并用于相应指标的事件。低流量版本不能把“没有告警”等同于“没有问题”。

BigQuery 导出能补分析能力,不能取消采样

Firebase Performance 支持把 captured events(已被采集并接受的事件)导出到 BigQuery,每一行对应一个 performance event。BigQuery 是 Google Cloud 的托管分析型数据仓库,适合使用 SQL 做长期留存和跨版本分析。导出数据已经经过端侧采样与限流,不是设备上全部事件的原始副本。

首次启用导出后,初始数据传播最多可能需要 48 小时;之后的常规同步任务通常会在安排执行后的 24 小时内完成。BigQuery 提供分析和持有数据副本的能力,不提供自托管采集入口;字段 schema(表结构和字段定义)也由 Firebase 制定。

和 JankStats、FrameMetrics、Android Vitals 的分工

这些工具都会出现渲染或稳定性指标,但观察对象不同:

工具主要样本长处不足
Firebase PerformanceActivity/Fragment screen instance、network、custom trace接入快,可看版本、设备和地区分布固定 60 Hz 阈值;缺少逐帧状态
JankStats逐帧 jank(卡顿帧)判定 + StateInfo 状态标签能关联 route、滚动和业务状态需要自行聚合、存储和上报
FrameMetricsWindow 级帧阶段耗时能观察 layout、draw、同步和 GPU 等阶段API 24+;数据较底层,需要自行解释
Android VitalsGoogle Play 分发人群的慢帧、ANR 等质量数据适合设定发布质量门槛和评估用户影响只覆盖 Play 样本,业务上下文少

Firebase 的 16/700 ms、screen instance 占比与 Android Vitals 的用户或会话统计口径不能直接比较。看到百分比变化时,应先确认分母(计算该百分比所基于的样本集合)、刷新率、版本分布和采样窗口,再决定是否用 JankStats、FrameMetrics、Macrobenchmark 或 Perfetto 继续定位。

使用建议

适合采用

  • 希望用较低接入成本观察启动、页面和网络的版本趋势;
  • 产品运行地区可以稳定访问 Firebase 服务;
  • 能接受托管服务、采样、分钟级控制台延迟和 Firebase 定义的字段 schema;
  • 愿意为首页可用、登录、图片解码、数据库查询等关键路径设计少量 custom trace。

需要配合其他工具

  • 秒级错误告警:使用业务错误码、日志或实时 APM(Application Performance Monitoring,应用性能监控)系统;
  • 逐帧页面状态:使用 JankStats;
  • 启动与渲染阶段:使用 Macrobenchmark、FrameMetrics 和 Perfetto;
  • ANR(Application Not Responding,应用无响应)和 native 故障信息:使用 Android Vitals、系统 trace、tombstone(native 崩溃转储文件)与内部采集;
  • 自托管或完整样本回溯:选择满足数据所有权要求的自建或商业方案。

接入与验收清单

  • 锁定 BoM、Performance plugin 和 Google services plugin 版本;当前复核值分别为 34.17.02.0.24.5.0
  • 分别配置构建插桩开关与运行时采集开关。
  • 确认只依赖主进程数据,没有把远程进程误算为已覆盖。
  • 在测试构建核对 _app_start、screen trace 和受支持网络请求。
  • 为 Cronet、native 或自研网络 client 增加独立 HttpMetric
  • 按资源族设计 URL pattern,禁止动态 id、query 和 token 进入维度。
  • custom trace 的 attribute key 遵守最长 32 字符的公开文档规则,attribute 不含 PII。
  • 把采样、展示延迟和 100 样本告警门槛写进看板说明。
  • 按需启用 BigQuery,并确认导出对象是 captured events,不是全部端侧事件。
  • Android 17 发布前用真实 90/120 Hz 设备检查 Firebase 固定 60 Hz 口径的偏差。

从 Firebase 迁移时应保留的字段

迁移到自建或商业 APM 时,不要只复制 trace 名。至少保留这些分析字段的定义:

  • app id、version name、version code、build type、distribution channel;
  • device model、OS/API level、country/region、network type;
  • trace name、start time、duration、metric、取值少且固定的 attribute;
  • URL pattern、HTTP method、status code、payload size、Content-Type;
  • screen name、total/slow/frozen frame count 及其阈值;
  • SDK/plugin version、采样率、限流规则、客户端采集时间和服务端接收时间;
  • 错误类型、重试次数、服务端 request id 的脱敏规则,以及它与服务端日志的关联方式。

应把字段名、单位、计算公式、分母和采样规则写成可版本化的数据字典,也就是一份说明每个字段含义和统计方式的规范。缺少这份规范时,迁移前后的曲线即使同名,也可能没有可比性。

源码核对点

结论firebase-perf:22.0.6 对应实现
_app_start 的起点和终点AppStartTrace.logAppStartTrace() 使用 Firebase class-load timer 到首次 onResume()
API 24+ 进程起点Process.getStartElapsedRealtime() 进入实验性 TTID trace,不是稳定 _app_start 起点
API 34+ 后台启动过滤AppStartCause 读取 ActivityManager.getMyMemoryState() 的 importance(进程重要性等级)
帧阈值Constants.SLOW_FRAME_TIME=16FROZEN_FRAME_TIME=700
Fragment 统计方式FragmentStateMonitor 配合 Activity FrameMetricsRecorder 计算区间差值
attribute 端侧校验PerfMetricValidator.validateAttribute();其宽松边界不能替代公开文档约束
URL 脱敏NetworkRequestMetricBuilder.setUrl() 调用 Utils.stripSensitiveInfo()

商业平台的信号覆盖与工程取舍

商业平台通常增加崩溃、会话、服务端链路或厂商生态能力。选型时需要用同一场景验证字段、采样、符号化和数据保留。

商业平台买的是服务能力和维护成本

APM(Application Performance Monitoring,应用性能监控)用于收集和分析 App 的稳定性、耗时与运行上下文。Sentry、APMPlus、Bugly 这类商业平台的付费内容主要包括 SDK、服务端、看板、告警、权限、符号文件、数据保留、工单协作和技术支持。符号文件包括 R8/ProGuard 的 mapping 与 native symbol,它们用于把混淆名或 native 地址还原成可读堆栈。团队由此减少采集后端维护、值班运营、告警规则维护和跨端数据分析工作。

选型前要先确认团队缺少哪种能力:崩溃治理、性能指标、用户会话回看、跨端追踪、国内访问、合规审计、私有化部署,或数据迁移。商业 APM 接入后会进入 App 启动、异常捕获、网络、页面和用户标识等敏感路径,采购评审必须同时看能力、成本和退出方式。

以下 Android 端版本按 2026 年 8 月 14 日的公开文档与发布仓库核对。PoC(Proof of Concept,小范围可行性验证)必须使用项目实际拿到的 artifact(仓库发布的 SDK 制品)、合同能力表和部署清单;商业合同也可能提供不同于公开仓库的分支。

平台核对的公开 Android 制品版本边界
Sentryio.sentry:sentry-android:8.53.0、Android Gradle plugin 6.19.0core AAR 的 minSdk=21;Session Replay 源码只在 API 26+ 启用;API 35+ profiling 使用 ProfilingManager
APMPlus 国内版apm_insight:1.5.25.cnapm_insight_crash:1.5.21、plugin 1.4.2国内与海外制品、上报地域不同,不能混用
APMPlus 海外版apm_insight:1.5.24.overseaapm_insight_crash:1.5.21.oversea当前公开接入页写明上报到马来西亚柔佛
Bugly ProMaven Central 最新为 com.tencent.bugly:bugly-pro:4.4.7.1616KB page size 要选择 com.tencent.bugly_16kb 下的同版本;公开更新日志目前只说明到 4.4.7.8

仓库中的最新版本只证明制品可下载,无法补足厂商尚未公开的行为说明。对 Bugly 4.4.7.8 之后、截至 4.4.7.16 的版本做能力判断时,应以实际 AAR、合同说明和 PoC 结果为准。

Android 17 / API 37 没有一套通用于所有商业 APM 的新采集协议。需要验证厂商 SDK 在 API 37 上使用的公开 API、native library(C/C++ 等本地代码库)、前后台判断、网络插桩和采样行为。“兼容 Android 17”这句宣传本身不足以形成可复现的验收结论。

三个平台的定位

Sentry:错误监控起家,移动端 APM 能力逐步补齐

Sentry Android 除了错误捕获,还支持 tracing(记录跨操作的调用链)、profiling(抽样调用栈)、Session Replay(会话画面回放)、logs、user feedback 和 release health(按发布版本统计会话与故障)。Gradle plugin 负责上传 source context(出错位置附近的源码)、mapping、native symbol 等构建产物;运行时 SDK 负责事件、span、profile 和 replay。两者的版本与开关要分别管理。

它适合这些团队:

  • 已经用 Sentry 管 Web、后端或 iOS 错误,希望移动端统一入口。
  • 需要异常、performance transaction、profiling 和 release health 放在一起看。
  • 面向海外用户,Sentry SaaS(由厂商托管的在线服务)可稳定访问。

从 SDK 8.51.0 起,Sentry UI Profiling 会按系统版本选择两条路径:Android 15(API 35)及以上调用系统 ProfilingManager,结果是 Perfetto trace;API 34 及以下默认回退到旧 ART runtime tracer。8.53.0 仍采用这套分支。设置 enableLegacyProfiling=false 可以关闭旧设备回退,也会关闭所有 transaction-based profiling;API 35+ 的 ProfilingManager UI Profiling 不受该开关影响。

两条路径都需要低采样,但风险不同。系统会对 ProfilingManager 请求限流,因此 API 35+ 即使命中 SDK 采样,也不保证每次请求都有 profile;这个后端也不支持 app-start profiling。旧 ART tracer 才涉及 Sentry 文档列出的 runtime crash 风险。如果 API 34 及以下新增 crash 集中在 libart.soart::Trace::StopTracingpthread_getcpuclockid 等 runtime 栈附近,应先关闭 legacy profiling 或降低采样率,再按 Sentry SDK、Android 版本和机型分组复现。升级 SDK 也要重新做灰度(先只向少量用户发布),不能把这些 runtime crash 全部归因给业务 native code。

Sentry Android 各能力存在 SDK/API 版本门槛,接入前要按版本表核对:

能力最低 SDK / API 版本边界与约束
Session ReplaySentry Android SDK 7.12.0+;运行时 API 26+默认遮盖文本、图片和 WebView;PixelCopy 仍可能出现遮盖位置偏差
UI ProfilingSentry Android SDK 8.7.0+ProfilingManager 后端需要 8.51.0+ 和 API 35+manual 与 trace lifecycle 两种模式互斥;API 21~34 默认使用 legacy ART tracer
Transaction-based profilingSentry Android SDK 6.16.0+、API 22+legacy 能力,单次最长 30 秒;Sentry 文档建议迁移到 UI Profiling
App start profilingSentry Android SDK 7.3.0+仅 legacy profiler 支持;API 35+ 的 ProfilingManager 后端忽略该开关;安装后的首次运行不会执行

Sentry 的主 profiling 页和 8.51.0 changelog 都把 ProfilingManager 的起始版本写为 8.51.0,但 legacy 页导语目前写成 8.47.0。这里采用有对应发布记录的 8.51.0 作为可验证边界。

Session Replay 默认遮盖不代表已经满足业务合规要求。默认 PixelCopy 策略通过 Android 异步截图 API 取图,再依据 View hierarchy(界面控件树)计算遮盖位置,二者时序不一致时可能错位;实验性的 Canvas 策略在重绘时遮盖文本和图片,更可靠但开销更高。SurfaceView 把内容绘制到独立 surface,需要单独开启实验性采集,而且只能整体处理,无法遮盖其中某个地图标签、视频帧或 Unity 元素。涉及支付、健康、聊天或身份信息的页面,应在 PoC 中逐屏检查录制结果。8.53.0 修复了一种硬件视频编码器卡住后引发 ANR 的问题,但这项修复不能替代目标机型上的 replay 稳定性测试。

APMPlus:国内移动 APM 平台型方案

APMPlus 是火山引擎的应用性能监控产品,覆盖 Android、iOS、鸿蒙、Web、PC、服务端等多平台。公开文档中 App 侧能力包括崩溃、卡顿、内存、网络、启动、自定义事件、日志回捞、报警和自定义看板等。日志回捞指平台按授权和配置,让指定设备补充上传故障附近的本地日志;它涉及的权限与数据范围需要单独审核。

它适合国内业务、需要托管平台和移动端专项能力的团队。当前 Android 接入页把国内与海外 artifact 分开:国内版上报到中国,海外版上报到马来西亚柔佛。数据地域应以合同、网络抓包和实际项目配置三方核对,不能只看依赖后缀。

公开验证页还给出了几个边界:crash 默认 100% 上报;其他监控项要命中平台采样配置;ANR(Application Not Responding,应用无响应)需要同时接入 crash 组件,只有性能组件时看不到 ANR 日志;网络自动监控依赖 Gradle plugin 和对应网络开关。PoC 设备应加入测试白名单,使其强制进入采集范围,或把目标模块临时调到 100%;否则“没有数据”可能只说明设备没有命中采样。

选型时要验证 SDK 支持的 Android 版本、targetSdk、ABI(应用二进制接口,此处主要指 arm64-v8a 等 CPU 架构)、主流网络库,卡顿 / ANR / OOM(Out of Memory,内存不足)/ native crash 的采集口径,符号文件与版本的绑定方式,日志回捞授权流程,以及远程采样和阈值调整能力。若采购专有云或私有化形态,应让厂商给出采集网关、消息缓冲、计算、查询存储、对象存储、冷热分层、备份恢复的实际 BOM(Bill of Materials,组件与资源清单)和容量模型;不能根据同厂商其他产品推测 APMPlus 使用了哪种数据库或流处理组件。

Bugly:普通版和 Pro 版要拆开评估

Bugly 普通版更偏 crash、ANR、符号文件和版本稳定性看板。公开普通版 Android changelog 的最新条目仍是 3.4.4(2021 年),不能拿普通版文档推断 Bugly Pro 4.4.x 的 API 或能力。

Bugly Pro 不能按普通版边界评估。Pro 版公开文档覆盖 crash、ANR、OOM、卡顿、FPS(Frames Per Second,每秒帧数)、内存、启动和页面回放等能力。ANR 有两组容易混淆的配置:

  • enableAllThreadStackAnr=true:ANR 发生时抓取线程堆栈,当前 builder 文档标为默认开启;4.4.6.2 的更新说明写明全线程抓取时不再重复抓主线程。
  • setEnableRecordAnrMainStack(true):记录 ANR 发生前的主线程堆栈,4.4.7.3 新增,当前示例默认 false

这两者采集时点不同。控制台缺少某份 stack(线程调用栈)时,要先核对配置、系统是否在采集完成前终止进程,以及当前机型能否取得 ANR trace。

选型表

平台更适合能力边界数据与部署成本和退出点
Sentry海外业务、跨端错误监控、tracing / profiling / replay 统一profiling 和 replay 必须低采样;API 34 及以下的 legacy ART 风险、API 35+ 的系统限流、遮盖可靠性、国内访问和 PII(可识别个人的信息)规则要分别核验SaaS 与 self-hosted(自行部署)在功能、升级和支持责任上不同按事件量、seat(付费账号席位)、保留周期、附件、profile 和 replay 用量估算;退出时导出 issue、release、event、trace、alert
APMPlus国内业务、移动端性能和稳定性一体化平台启动、卡顿、ANR、OOM、网络、内存、日志回捞、单点查询、报警和看板较全公开接入页区分中国与柔佛上报;专有云或私有化能力以合同为准费用与事件量、留存、日志回捞、部署资源、支持服务相关;退出时迁移指标口径和 dashboard
Bugly Regular国内稳定性治理、崩溃 / ANR / 符号文件偏稳定性入口,性能能力按套餐确认SaaS 为主,和腾讯生态流程结合较深接入成本低;退出时要处理 crash issue、mapping、symbol、Webhook(HTTP 回调)和版本趋势
Bugly Pro需要卡顿、内存、启动 Span、页面回放等增强能力Maven 最新是 4.4.7.16,公开能力说明只到 4.4.7.8;远端配置要逐项验收,16KB 还要选对 groupId关注 ANR 诊断字段、replay、Span 数据和合同部署形态费用和数据量、采样、保留周期相关;退出时迁移诊断字段、附件和 Span 数据

选型结论不要只看功能列表。商业平台越深入 App 运行路径,越要确认数据归属、字段合规、留存周期、费用模型、16KB Page Size 适配和退出成本。

商业 APM 的评审维度

16KB Page Size 验收包含两个层次:.so 的 ELF load segment 要按 16KB 边界对齐,APK 中未压缩 .so 的 ZIP 存放位置也要对齐。ELF 是 native library 的可执行文件格式;APK 是安装包,AAB 是交给应用商店生成 APK 的发布包。两个检查都通过,才能说明最终交付物满足 16KB 加载要求。

维度要问的问题验收方式
SDK 覆盖Android 版本、targetSdk、ABI、主流网络库、Flutter / React Native(RN)/ WebView 是否支持用试点 App 接入,覆盖 release、debug、混淆和包含多种 ABI 的包
稳定性SDK 自身 crash、ANR、启动开销、线程数、包体积先发布给 1% 用户,跟踪 SDK crash、启动 P95(95% 样本不超过的耗时)、主线程耗时和包体积增量
16KB Page Size 兼容(Android 15+)SDK 及其传递依赖中的 .so 是否支持 16KB ELF alignment,APK/AAB 中未压缩 native library 的 ZIP alignment 是否正确用 16KB 模拟器或真机运行 release 包;执行官方 check_elf_alignment.sh,再用 zipalign -c -P 16 -v 4 检查 APK;关注加载失败与 native crash
性能数据启动、慢帧、卡顿、ANR、OOM、网络、磁盘、功耗是否有清晰口径用已知慢帧、弱网、OOM、ANR 样本回放,核对平台展示与本地 trace / log 是否一致
故障证据堆栈、日志回捞、trace、截图、session replay、用户路径检查是否有授权流程、脱敏规则、采样上限和故障时的人工取证路径
符号化ProGuard mapping、native symbol、版本和 build id(一次构建的唯一标识)绑定用一个已知混淆 crash 和一个 native crash 验证堆栈还原率
采样配置远程开关、按版本 / 机型 / 页面采样、异常强制采样灰度配置后核对生效延迟、回滚延迟和误采样率
数据所有权原始事件、聚合指标、附件、replay、日志、trace 属于谁合同写明导出格式、保留周期、删除 SLA(约定的完成时限)、离职权限回收
部署形态SaaS、专有云、私有化的数据边界和网络路径画出数据流向图,标出端上采集、网关、存储、计算、看板、审计
价格模型license、seat、事件量、日志量、回放量、留存周期、私有化资源做 3 档流量估算:当前量、2 倍峰值、活动峰值
迁移成本schema(字段结构)、trace 名、alert、dashboard、mapping、历史数据能否迁出试导出 7 天数据,导入内部仓库或另一平台做字段映射
合规数据区域、PII 过滤、保留周期、访问审计、删除流程由法务 / 安全 / 隐私团队按字段清单签字
运营告警、负责人分配、工单、Webhook、报表导出用一次演练验证谁收到告警、谁看样本、谁确认修复

Sentry 的 transaction 和 profiling

Sentry 的性能模型围绕 transaction / span 展开。transaction 表示一次根操作,例如页面加载;span 表示其中较小的计时区间,例如网络、解析或渲染。多个相关 transaction 和 span 组成一条 trace,移动端事件由此可以和后端服务路径关联。

下面的结构把页面加载作为根 transaction,把网络、解析和渲染作为子 span:

transaction: HomeScreen.load
  span: http GET /feed
  span: json.parse
  span: db.read_cache
  span: ui.render

这些 span 的名称必须稳定。动态 URL、feed id 或 user id 会产生大量不同名称,不应拼进 span description;确有分析需要时,应放入经过隐私审核且取值受控的 attribute/tag。

下面的 Kotlin 示例确保子 span 和根 transaction 在成功、异常两条路径上都会结束:

import io.sentry.Sentry
import io.sentry.SpanStatus

fun loadHome(): Feed {
    val transaction = Sentry.startTransaction("HomeScreen.load", "ui.load")
    return try {
        val networkSpan = transaction.startChild("http.client", "GET /feed")
        try {
            api.loadFeed().also {
                networkSpan.status = SpanStatus.OK
            }
        } catch (t: Throwable) {
            networkSpan.throwable = t
            networkSpan.status = SpanStatus.INTERNAL_ERROR
            throw t
        } finally {
            networkSpan.finish()
        }
    } finally {
        transaction.finish()
    }
}

漏掉任意一次 finish() 都可能让 span 缺失或 duration 失真。生产封装应把 try/finally 放进 facade(内部适配层),让业务调用者只提供待计时的代码 block。

如果后端也接了 Sentry 或 OpenTelemetry(跨厂商的可观测数据标准),移动请求携带 trace headers(传播 trace 标识的 HTTP header)后,可以从 App span 关联到服务器路径。应通过 tracePropagationTargets 只允许自有 API host;host 在这里指服务器域名。不要把 sentry-trace 或 baggage(随 trace 传播的附加键值)发给广告、支付等第三方域名。客户端与后端的采样规则也要一起核对;一端有数据、另一端缺失,可能来自两端各自的采样决定。

Tracing、profiling、Session Replay 和附件要使用彼此独立的采样预算。接入评审里要单列 tracesSampleRate/sampler(按上下文决定是否采样的函数)、profileSessionSampleRate、replay 的 sessionSampleRateonErrorSampleRate,还要记录回放时长、脱敏规则、附件大小、丢弃原因和上传失败策略。1.0 适合白名单验收设备,不适合作为默认生产配置。

APMPlus 的移动专项能力

国内商业 APM 的优势是贴近 Android App 线上治理常见问题:崩溃、ANR、卡顿、启动、网络、内存、日志回捞、单点查询、报警和 SDK 远程配置。

APMPlus 公开文档中的“卡顿分析”监控主线程 message 执行超时;这里的 message 是 Looper 消息队列中一次待执行任务。默认卡顿阈值为 2.5 秒、严重卡顿为 4 秒。“流畅性/丢帧”属于另一组数据,不能与 Android Vitals 慢帧、JankStats jank 或系统 ANR 混成一个指标。接入时要验证这些问题:

  • ANR 是系统 ANR、SDK 自判卡死,还是两者都有。
  • 卡顿是 Looper message timeout、慢帧,还是方法 trace;各自阈值和分母是什么。
  • 内存是 OOM、泄漏、PSS(Proportional Set Size,按共享比例分摊后的进程物理内存)、Java heap,还是 native 内存。
  • 日志回捞是否按用户授权和配置触发。
  • SDK 采样是否能按版本和灰度动态调整。
  • 私有化是否给出存储容量、查询 QPS(Queries Per Second,每秒查询数)、冷热分层(近期高频数据与历史低频数据分开存放)、备份恢复和升级窗口。

网络模块的公开接入示例通过 ApmPlugin.okHttp3Switch 开启 OkHttp3 插桩。若应用使用 OkHttp 4/5、Cronet(基于 Chromium 的网络引擎)、native stack 或自研 client,应使用真实请求核对;“网络分析”这个功能名称不能证明所有协议都被覆盖。

这些名词在不同平台里的口径可能不同。合同和接入文档里要写明起止点、阈值、采样、上报时机和聚合分母,否则多个控制台即使显示同名指标,也无法比较。

Bugly 的稳定性边界

Bugly 普通版常见价值在:

  • Java crash 聚合。
  • Native crash 符号化。
  • ANR 上报。
  • 版本维度趋势。
  • mapping / symbol 管理。
  • Webhook 对接内部流程。

Bugly Pro 的评审口径要扩到性能监控:ANR 发生时的线程栈、发生前主线程记录、卡顿高频抓栈、启动 Span 是不同数据源,必须分别构造样本。启用这些能力前,要确认远端开关、采样率、低端机开销、上报时机、数据留存和 16KB artifact。当前 Maven Central 最新版本是 4.4.7.16,但公开 changelog 最后一条仍是 4.4.7.8;对这之后、截至 4.4.7.16 的版本,不能按版本号猜测新增或修复内容。

公开 Android 接入页写明 crash、ANR、OOM 默认 100% 上报且不支持采样,其他性能监控项支持采样。这个差异会直接影响事件费用、流量与隐私评审,不能用“统一采样率”估算 Bugly Pro。

如果团队只需要 crash / ANR 基础设施,普通版可能足够。如果要把 Bugly 当完整性能平台,要按 Pro 能力做 PoC,不要用普通版经验推断 Pro 版边界。

Bugly Pro 各增强能力存在 SDK 版本门槛,PoC 前要确认当前集成版本是否覆盖:

能力最低 SDK 版本PoC 验证动作
页面启动耗时Android SDK 4.4.3+核对 Activity 的渲染耗时、加载耗时与本地时间点
页面启动 SpanAndroid SDK 4.4.3.5+检查 startSpan() / endSpan() 成对;同名 span 后写会覆盖前写
ANR 发生时线程栈当前 builder 的 enableAllThreadStackAnr=true检查 ANR 详情中的线程栈;主线程可能由独立字段呈现
ANR 前主线程记录Android SDK 4.4.7.3+setEnableRecordAnrMainStack(true)对比关闭与开启后的 ANR 前主线程样本
页面回放Android SDK 4.4.7.3+检查二次启动后的附件上报、采样、敏感页面与数据遮盖
16KB Page Size4.4.6.2+ 开始提供独立 16KB artifact;当前 Maven 版本为 4.4.7.16依赖必须来自 com.tencent.bugly_16kb;对 release APK 做 ELF/ZIP alignment 和运行验证

Bugly 的 16KB 文档存在历史措辞差异:changelog 与 Android 接入页写的是从 4.4.6.2 开始提供,单独的升级指南又以 4.4.6.4 为示例。当前可复现的做法是使用 Maven Central 已发布的 com.tencent.bugly_16kb:bugly-pro:4.4.7.16,再检查最终 release 包。groupId 是 Maven 坐标中的发布组织;只升级版本号但仍使用 com.tencent.bugly,不足以证明选择了 16KB 制品。

页面回放在当前文档中仍标为完善中的功能。它每秒采集一张 view hierarchy 与 screenshot,crash 后缓存为附件,等 App 二次启动再上传;截图采用整图马赛克,不是按字段证明敏感信息已被可靠识别。默认采样率是 0,文档建议 0.01~0.1。PoC 应检查 replay.zip 中的 JPEG 和 JSON 原始内容、资源开销、附件权限与删除流程。

以上门槛来自 Bugly Pro 官方 Android 接入页、更新日志和功能页。Bugly 是商业 SDK,版本阈值由厂商制品控制;当前集成版本低于门槛时,应先在独立分支升级并做小流量验证。

PoC 验收表

商业 APM 也要按小流量验证。试点时选一条完整路径:发现问题、查看样本、定位责任、验证修复、关闭告警。

验收项构造样本通过标准
Java crash构造一个已知异常,带混淆 mapping平台能聚合、还原符号、按版本和用户查询
Native crash构造一个测试 .so 崩溃,上传 symbol能显示 native 栈、build id、ABI、系统版本
ANR在受控测试包中用 input、BroadcastReceiver、Service 等超时路径构造系统 ANR,再补锁等待 / Binder 等待能区分系统 ANR 与 SDK 自判卡死;记录触发类型、主线程与相关线程栈
慢帧 / 卡顿构造 60Hz 和 120Hz 页面卡顿能给出慢帧时间、页面、设备、系统版本;与 Perfetto FrameTimeline 大体一致
启动冷启动、温启动各跑 30 次能按版本、渠道、机型看 P50(中位数)和 P95(第 95 百分位);采样对启动耗时影响可接受
网络阶段对每种受支持 client 构造 DNS 慢、connect 慢、TLS 慢、服务端慢文档承诺的阶段都能出现;未支持的 client 或协议有明确补点方案
OOM / 内存构造 Java heap 压力和 native 内存压力能拿到内存趋势、设备水位、进程存活信息;不把 LMK(系统因内存压力终止进程)误写成 Java OOM
采样准确性白名单设备发固定数量事件,再把采样率改为 25% 重复测试100% 配置下无系统性漏报;采样配置的生效时间、误差和服务端限流可解释
告警噪声构造一次低量级异常和一次集中异常告警阈值可控;不会因采样波动反复报警
低端机开销低端设备跑 30 分钟常用场景SDK 线程数、CPU、内存、流量、包体积增量在接入预算内
16KB Page SizeAndroid 15~17 的 16KB 环境运行 release 包并触发 crash / ANR / profiling所有 .so 的 ELF/ZIP alignment 通过;App 与 native 采集能力正常
删除与权限创建专用测试用户,产生 event、replay、log、attachment 后发起删除数据在合同 SLA 内删除;导出、审计和离职权限回收均有记录

成本模型与 ROI 估算

TCO(Total Cost of Ownership,总拥有成本)不只包含“每年多少钱”,还应拆成这些项:

成本项计算口径低估后的后果
license / 套餐App 数、平台数、MAU(月活跃用户数)、事件量、seat后续扩端或扩团队时费用跳涨
数据量crash、ANR、trace、log、replay、attachment 的月增量保留周期被迫缩短,线上样本查不到
私有化资源计算、存储、对象存储、消息队列、带宽、备份查询慢、告警延迟、活动峰值时丢数据
运维人力升级、容量、备份、权限、审计、值班平台买回来后仍要内部团队兜底
合规成本字段梳理、脱敏、删除流程、访问审计上线慢,或后续被安全团队叫停
迁移成本facade、schema、历史数据、dashboard、alert、mapping供应商更换时业务代码和看板一起返工

月度总成本可以按这个公式估:

月度 TCO = 平台订阅费用
         + 数据增量费用
         + 存储和保留费用
         + 私有化基础设施费用
         + 运维和值班人力成本
         + 合规与审计成本
         + 迁移预留成本

ROI(Return on Investment,投资回报)要用可比较的数据计算。可量化的收益包括:崩溃率下降带来的留存改善、ANR / 慢帧定位时间缩短、值班误报减少、内部 APM 后端维护人力减少、合规审计时间缩短。PoC 阶段至少记录“接入前定位一次线上 ANR 的耗时”和“接入后用平台样本定位同类问题的耗时”。

私有化责任表

私有化的交付范围包括内网部署,也包括部署、升级、存储、权限、审计、数据删除和故障责任。所有责任都要写进合同和验收文档。

事项厂商应交付客户侧负责验收材料
部署架构网关、采集服务、计算、存储、看板、告警的拓扑网络、域名、证书、Kubernetes(容器编排)/ VM(虚拟机)资源架构图、端口清单、容量模型
升级SDK 版本、服务端版本、兼容表、回滚方案升级窗口、灰度策略、回滚审批升级手册、回滚演练记录
存储热数据、冷数据、对象存储、备份恢复方案磁盘、备份介质、保留周期容量压测、恢复演练、保留策略
权限角色模型、项目隔离、审计日志组织架构、离职回收、最小权限权限表、审计样例
数据删除用户数据删除 API、批量清理任务删除工单、合规审批、回查流程删除 SLA、抽样验证记录
故障责任组件健康检查、告警规则、支持响应时间值班人员、基础设施故障处理SLA、RCA(Root Cause Analysis,根因分析)模板、演练记录
成本控制采样、保留、冷热分层、限流策略业务峰值预估、预算上限月度容量报表、费用报表

内部 APM facade 与字段合同

商业平台 SDK 不要直接散落在业务代码里。先定义内部 facade,也就是业务代码只依赖的统一监控接口,再由实现层映射到各平台 API。

data class MonitorContext(
    val pageName: String,
    val appVersion: String,
    val buildId: String,
    val experimentId: String?,
    val deviceTier: String
)

interface TraceHandle {
    fun addMetric(name: String, value: Long)
    fun addTag(name: String, value: String)
    fun finish(status: String)
}

interface AppMonitor {
    fun setUser(anonymousId: String?, userType: String)
    fun clearUser()
    fun setContext(context: MonitorContext)
    fun captureException(throwable: Throwable, tags: Map<String, String>)
    fun startTrace(name: String, context: MonitorContext): TraceHandle
    fun reportMetric(name: String, value: Long, tags: Map<String, String>)
}

实现层要保证 finish() 幂等,即重复调用也只结束一次,并提供内部 try/finally 包装,避免某个 vendor(平台供应商)要求手工结束 span 时产生没有结束时间的数据。anonymousId 应是经过隐私评审的假名标识:它可以关联同一主体的事件,但不直接暴露真实身份。退出登录时调用 clearUser(),不要把手机号、邮箱、广告标识符或可逆业务主键直接传给厂商。

字段合同要比代码接口更早定下来:

字段约束
page.name用产品页面名,不用 Activity 类名直接当展示名
user.type匿名、登录、会员、内测等有限枚举
user.id只允许经同意的假名标识;定义生成、轮换、删除和跨平台映射规则
app.version / build.id和 release、mapping、native symbol 一一绑定
experiment.id灰度、A/B、功能开关统一命名
network.stageDNS、connect、TLS、request、TTFB(Time to First Byte,收到首字节前的时间)、download 统一枚举
device.tier低端 / 中端 / 高端规则固定,避免迁移后设备分桶(按规则分组)发生变化
trace.name由内部词典生成,不直接使用 vendor 自动名

只要字段合同稳定,更换 Sentry、Firebase、APMPlus、Bugly 或自建平台时,业务侧不需要到处改 SDK API。

退出成本与迁移失败样本

商业平台还要评估退出成本:数据能否导出,事件 schema 是否能迁移,客户端 SDK 是否与业务代码耦合,自定义 trace 名称是否平台专有,告警和工单流程是否绑定平台。

迁移中常见的失败样本有三类:

样本现场表现规避方式
字段名漂移旧平台用 screen_name,新平台用 page;历史看板无法和新数据合并接入前建立内部字段词典,所有 vendor 字段都由映射层生成
符号文件断档旧平台保存了 mapping / symbol,新平台只有新版本符号;历史 crash 无法还原mapping、native symbol、build id 和 release 元数据单独归档,不能只放在 vendor 平台
告警规则丢失迁移后阈值、负责人、工单状态无法搬迁;值班噪声暴涨把告警规则导出成内部配置,迁移前做一次影子告警对照

迁移前要做一次 7 天影子运行:两个平台同时接收数据,老平台继续报警,新平台只记录、不通知值班人员。对比 crash 聚合数、ANR 数、慢帧 P95、网络错误率、告警数量、误报样本和缺失样本,再决定切流比例,也就是逐步把多少正式监控与告警转到新平台。

核验来源

商业 APM 的实现不是 AOSP 组成部分,无法用 Android 17 platform tag(AOSP 对应版本标签)验证厂商闭源逻辑。核验采用三层证据:

  1. 厂商公开接入页、功能页和 changelog,用于确认制品版本、开关和产品口径。
  2. 可下载 AAR 的 Manifest、source package(源码包)和最终 APK,用于确认 minSdk、API 分支、native library 与打包结果。
  3. Android 8~17 真机或模拟器的构造样本,用于确认运行行为、采样、上传、符号化和开销。

“文档写了支持”只完成第一层。采购验收要把第二、三层的制品哈希、测试包 build id、设备、系统版本和平台截图归档。

参考资料

Sentry

APMPlus

Bugly

Android