Appearance
APM 线上性能监控与稳定性体系建设
回到总览:性能、测试、排障
相关模块:Android ANR 触发机制、排障定位与防范 · Android / Java 内存泄漏定位、LeakCanary 原理与 Profiler 实战
一句话定义
APM(Application Performance Monitoring,应用性能监控)是指在应用 Release 线上环境,通过轻量级无侵入的 SDK 实时采集 Crash、ANR、内存、网络、卡顿与流量等多维度性能指标,经聚合上报与告警系统实现问题精准归因的稳定性防护体系。
代码索引
计划补齐的实验:
labs/android/host/topics/performance/apm-monitoring-demo/— 全局 UncaughtExceptionHandler 捕获、AnrObserver 监听与打点聚合上报
为什么需要
- 为什么线下测试完全没问题的 App,上线后在用户的某些低端机型上仍会大量抛出 UncaughtException 和 ANR?
- 一句话答:线下测试环境有限,无法覆盖生产环境中复杂的 Android 厂商定制 ROM、极端低端硬件配置、弱网环境以及用户千奇百怪的操作路径;必须依赖线上 APM 的真实用户监测(RUM)进行兜底分析。
- 为什么 10 年 Android 工程师建设 APM 时必须遵守“极低侵入与低资源消耗”原则?
- 一句话答:APM SDK 运行在用户真实设备上,如果监控 SDK 本身引发了频繁的内存分配、CPU 抢占或过多耗电,就会喧宾夺主导致业务性能劣化。
底层机制
1. APM 监控四大核心指标与捕获机制
text
┌── Java Crash: Thread.setDefaultUncaughtExceptionHandler()
├── Native Crash: Breakpad / Signal Catcher (SIGSEGV/SIGBUS)
┌── 1. 崩溃稳定性 (Crash) ┤
│ └── ANR: Signal 3 (SIGQUIT) / DropBox / AnrObserver
│
├── 2. 性能体验 (Performance) ── JankStats 丢帧率 / 启动 TTID / 内存堆 Peak
APM ─┤
├── 3. 网络与 API (Network) ── OkHttp Interceptor / 慢请求率 / 错误码分布
│
└── 4. 资源与大图 (Resource) ── Native 内存用尽 / Bitmap 尺寸溢出监控各指标捕获技术路线:
- Java Crash 捕获: 通过
Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler)拦截未捕获异常,记录堆栈后交给系统原生的 Handler。 - Native Crash (C/C++) 捕获: 注册 Linux 信号处理函数
sigaction(),捕获SIGSEGV(段错误)、SIGABRT(放弃信号)、SIGBUS(总线错误)等信号,通过Breakpad提取 minidump。 - 网络性能监控: 通过注入
OkHttp EventListener/Interceptor采集 DNS 解析耗时、TCP 握手耗时、SSL 握手耗时与响应 Body 尺寸。
Android / Flutter / Web / Backend 对照
| 监控维度 | Android 原生 | Flutter 框架 | Web / Backend |
|---|---|---|---|
| Crash 捕获 | UncaughtExceptionHandler / Breakpad | FlutterError.onError / PlatformDispatcher.onError | window.onerror / Sentry Node SDK |
| APM 平台 | 腾讯 Matrix / 字节 APM / Firebase | Sentry Flutter / Datadog | Web Vitals / Prometheus + Grafana |
| 网络插桩 | Bytecode Instrumentation (ASM) | Custom HttpClientAdapter | Service Worker / Fetch API Interceptor |
常见场景
1. 自定义未捕获异常兜底打点 (UncaughtExceptionHandler)
java
public class ApmCrashHandler implements Thread.UncaughtExceptionHandler {
private final Thread.UncaughtExceptionHandler mDefaultHandler;
public ApmCrashHandler() {
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
public void init() {
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
// 1. 采集上下文数据 (用户 ID、当前页面、内存剩余)
JSONObject crashData = collectCrashInfo(t, e);
// 2. 写入本地磁盘崩溃日志 (同步 Flush 写入)
saveToDiskSync(crashData);
// 3. 交还给系统原本的 Handler 展示崩溃弹框/退出
if (mDefaultHandler != null) {
mDefaultHandler.uncaughtException(t, e);
}
}
}常见误配、事故后果与排障
1. 事故:APM 日志实时同步网络上报造成死锁与二次 Crash
- 误配原因:在
uncaughtException()内部使用 OkHttp 或主线程发同步网络请求上报 Crash 数据。 - 后果:崩溃发生时线程可能处于不稳定状态或发生了 OOM,在 Crash Handler 中发网络请求会导致二次崩溃,导致最原始的 Crash 堆栈丢失。
- 排障与修法:崩溃触发时只做同步磁盘写入(使用 mmap / mmkv 保障写入),将网络上报逻辑留到 App 下一次冷启动时在后台线程异步补发。
与相近概念对比
| 监控机制 | 触发时机 | 性能损耗 | 典型方案 |
|---|---|---|---|
| 被动指标捕获 (Crash/ANR) | 异常事件发生时 | 近乎 0 | UncaughtExceptionHandler |
| 主动轮询监控 (Memory/FPS) | 定时器周期触发 (如 1s) | 低 | FrameMetrics / 线程池轮询 |
| 字节码插桩 (ASM/AspectJ) | 编译期插入 Trace 点 | 中/低 | 字节 Matrix / 听云 / NewRelic |
对应实验
计划补齐的实验:
labs/android/host/topics/performance/apm-monitoring-demo/— 全局 UncaughtExceptionHandler 捕获、AnrObserver 监听与打点聚合上报
复习检查题
在崩溃监控中,为什么 Crash 日志绝对不能在
uncaughtException回调里直接发网络请求上报?答:当发生
UncaughtException时,应用线程处于不稳定的崩溃临界状态(甚至可能是发生了内存溢出 OOM 或主线程死锁)。在回调中发起网络请求需要申请新的内存、创建 Socket 和线程,极易引发二次崩溃,导致原始的 Crash 堆栈丢失。正确的做法是仅同步写入磁盘(或 mmap 文件),待应用下一次启动时再异步上报。针对 C/C++ 层的 Native Crash(如段错误
SIGSEGV),Java 层的UncaughtExceptionHandler能捕获到吗?答:捕获不到。Native Crash 发生在 C/C++ 层,由 Linux 内核直接向进程发送信号(Signal)。Java 层的异常处理机制只作用于 JVM/ART 抛出的 Java 异常。捕获 Native Crash 必须在 C/C++ 层通过
sigaction()注册信号处理函数,或使用Google Breakpad提取 minidump 堆栈。
速记
- 崩溃防丢失:Java 抓 UncaughtException,Native 抓 SIGSEGV 信号,日志先落盘下回报。
- APM 规则:极低侵入与性能损耗,严禁监控 SDK 本身引发卡顿与二次 OOM。