前言
ANR 之后的最大杀手是 Crash。本篇聊完整的崩溃监控与稳定性工程:从 Crash 发生、上报、聚合、定位、修复、回归。一个 100 万 DAU 的 App,每天能看到 5K-50K 次 Crash,治理不好就是日复一日的负面评价。
一、Crash 的分类
二、Bugly vs Firebase Crashlytics vs 自建
中小团队推荐 Bugly + 自建(前后端配合);海外选 Crashlytics;重视隐私/可控的选自建。
三、Bugly 上手与最佳实践
3.1 集成
// build.gradle.kts
implementation("com.tencent.bugly:crashreport:5.0.1")
implementation("com.tencent.bugly:nativecrashreport:4.0.3")
// App.onCreate
CrashReport.initCrashReport(this, "bugly-app-id", BuildConfig.DEBUG)
CrashReport.setUserId("user_$userId")
CrashReport.setDeviceId(AndroidId)
CrashReport.setDeviceModel(Build.MODEL)
3.2 自定义 key-value
CrashReport.putUserData(this, "page", currentPage)
CrashReport.putUserData(this, "channel", "huawei")
崩溃发生时,这些值会跟着上报,定位时能筛"特定渠道/页面"的问题。
3.3 上传 symbol 文件
// 设置 Bugly symbol 上传任务
bugly {
appId = "xxxx"
uploadSymbol = true
symbolDir = file("build/native_libs").absolutePath
}
Native crash 必须有 symbol 才能看到正确栈。
四、自建 Crash 监控系统的架构
┌──────────────────────────────────────┐
│ App 端 │
├──────────────────────────────────────┤
│ Crash Handler │
│ - Thread.setDefaultUncaughtHandler │
│ - SIGSEGV / SIGBUS / SIGABRT │
│ - ANR Detector │
│ - Local Buffer (offline tolerant) │
└────────┬─────────────────────────────┘
│ HTTPS (带重试)
▼
┌──────────────┐ ┌────────────────┐
│ Collector │───▶│ Kafka/Queue │
└──────────────┘ └────────┬───────┘
▼
┌────────────────┐
│ Storage │
│ (ClickHouse/ES)│
└────────┬───────┘
▼
┌────────────────────────────────────┐
│ Aggregation / Analysis │
│ - Stack hashing dedup │
│ - Trend / first / repeat crash │
│ - Affected devices / versions │
└─────────┬──────────────────────────┘
▼
┌───────────────┐
│ Dashboard │
│ / Alarm system │
└───────────────┘
4.1 上报字段
每条 crash 至少带:
{
"crash_id": "uuid",
"timestamp": 1234567890,
"app_version": "1.0.0",
"app_build": 12345,
"device": "Xiaomi 14",
"os_version": "Android 14",
"stacktrace": "...",
"thread_state": { "name": "main", "priority": 5 },
"user_id": "...",
"extra": { "channel": "huawei", "page": "feed" }
}
4.2 Crash 哈希去重
把 stacktrace 头部 5 行做 SHA1,得到聚合 key:
fun hashStack(stack: String): String {
val lines = stack.lines().take(5)
val digest = MessageDigest.getInstance("SHA-1")
lines.forEach { digest.update(it.toByteArray()) }
return digest.digest().joinToString("") { "%02x".format(it) }
}
这样同一个根因的 crash 会聚合到一起。
4.3 离线/网络异常处理
Crash 上报要走 fallback:
class CrashReporter(private val api: CrashApi) {
private val cacheFile = File(context.cacheDir, "crash_queue.jsonl")
fun init() {
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
saveCrashToCache(thread, throwable)
flushCache()
}
// 同时启动定时 flush
scheduler.scheduleAtFixedRate(::flushCache, 0, 30, TimeUnit.SECONDS)
}
private fun flushCache() {
if (!isNetworkAvailable()) return
cacheFile.useLines { lines ->
lines.forEach { line ->
runCatching { api.upload(line) }
}
}
cacheFile.writeText("")
}
}
五、Bugly 控制台实战
5.1 必看的几个视图
5.2 关键指标
一个 100 万 DAU App,每天 Crash 应控制在 1000 次以内。
5.3 Issue 创建流程
Crash 阈值报警触发
从控制台拉 stacktrace、版本、设备分布
查 git blame,找到具体代码行
创建工单,owner 分配
修复 + 加测试用例
灰度发布,验证 Crash 量下降
六、稳定性专项治理
6.1 Crash Free Sessions
Crashlytics 等 APM 平台的指标:
CrashFreeSessions = 1 - (CrashSessions / TotalSessions)
6.2 Top Crash 治理流程
每周末做一次 top crash 复盘:
6.3 黄金信号与 SLO
可用性 SLO:
- Crash Free Sessions: 99.95%
- ANR Free Sessions: 99.9%
- 启动成功率: 99.5%
告警阈值:
- 任意 crash count +50%(vs 上周均值) → 触发报警
- Crash Free 跌到 99.5% 以下 → 触发报警
七、Crash 防御工程
7.1 全局 try-catch
class SafeExecutor {
companion object {
fun <T> safely(block: () -> T): Result<T> = runCatching(block)
.onFailure { crashReporter.report(it, level = NON_FATAL) }
}
}
这样非关键路径的崩溃会被捕获,转为非致命错误上报。
7.2 StrictMode 检测
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
}
StrictMode 会在 debug 包里捕获主线程 IO、网络、磁盘操作,提前预警。
7.3 Crash 测试
写一个"Crash 测试用例":
@Test
fun testCrashReported() {
// 模拟崩溃场景,确保被捕获
assertThrows(RuntimeException::class.java) {
throw RuntimeException("test")
}
}
CI 里跑这用例,确保 crash handler 链路有效。
八、Native Crash 定位
JNI 崩溃比 Java 崩溃难定位。流程:
# 1. 用 addr2line 反向解析
addr2line -e libnative.so -f <address>
# 2. 用 ndk-stack 工具
ndk-stack --sym ./app/src/main/obj/local/arm64-v8a \
--dump crash.dump
# 3. 用 AS Memory Profiler 看 native heap
如果用了第三方 so,记得开 Bugly native 上报:
implementation("com.tencent.bugly:nativecrashreport:4.0.3")
九、稳定性工程的"长期指标"建设
每季度对稳定性指标做一次复盘。
十、从"事后修"到"事前防"
SRE 思路引入移动端稳定性工程,多能把这套做成日常运转。
小结
稳定性工程不止是"出了 crash 修一下"。它包括事前防御(Lint/StrictMode)、事中捕获(Bughly/APM)、事后分析(聚合/告警)、长期建设(SLO/复盘)。一个团队至少要有 1 位 engineer 把 20% 的工作时间投入到稳定性工程里,否则 Crash 会越来越难收场。
下一篇聊 Android 启动框架——App 冷启动链路里能优化的所有点,以及"业务 1 秒启动"是怎么做到的。
参考阅读
Tencent Bugly 官方文档
Firebase Crashlytics 集成指南
SRE 系列:Google SRE Workbook