崩溃与 ANR(Crash & ANR)
崩溃(Crash)是移动开发者的第一痛点:一次未捕获的异常就是一次用户流失。Argus 在五个端上
自动捕获崩溃——原生信号、未捕获异常、未处理的 Promise rejection——把同一个 bug 的多次发生
聚合成一个 Issue,告诉你它影响了多少用户、集中在哪个版本 / 系统,并通过符号化
(symbolication)把十六进制地址栈还原成 file:line 源码级堆栈。
除了「进程死掉」的崩溃,Argus 还捕获「进程活着但没响应」的问题:主线程阻塞超过阈值 (默认 700ms)时,watchdog 异步抓取主线程栈上报为卡顿事件(AppHang),与应用无响应 (ANR,Application Not Responding)问题同源,走与崩溃相同的聚合 / 展示通道。
崩溃采集是 SDK 最危险的代码——它运行在进程已崩溃、内存状态可能损坏的现场。Argus 的 处理铁律是:现场只做 async-signal-safe 的原始落盘,符号化与上报推迟到下次启动 (defer-to-next-launch),并有二次崩溃保护与坏数据隔离,绝不因为「上报崩溃」而制造新的崩溃。
核心概念
Issue 与指纹(signature)
同一个 bug 会在不同设备上反复崩溃。Argus 对每个崩溃事件计算指纹
(signature,16 位十六进制,sha256[:16]):基于栈顶应用帧 + 异常类型,并做地址归一
(消除 ASLR 随机基址)与消息归一(order 12345 与 order 67890 视为同一 bug)。
同一 signature 的全部事件 = 一个 Issue,Console 与 MCP 工具都以 signature 为聚合键。
error_type:崩溃、卡顿与 OOM 走同一通道
崩溃事件用 error_type 区分成因,常见取值:
| error_type | 含义 | 来源 |
|---|---|---|
SIGSEGV / SIGABRT / SIGBUS / SIGILL / SIGFPE | 原生信号崩溃 | iOS / Android 信号处理器 |
NSException 类名 / Kotlin·Java 异常类名 | 运行时未捕获异常 | NSException / Thread.UncaughtExceptionHandler |
JS 错误类名(如 TypeError) | 未捕获 JS 异常 / Promise rejection | Web / React Native |
AppHang | 主线程阻塞 ≥ 700ms(含卡顿时的主线程栈与 hang_duration_ms) | iOS / Android watchdog |
OOM | 内存不足被系统杀死 | Android ApplicationExitInfo(API 30+)下次启动补报;iOS 启发式 |
Android 系统级 ANR 的判定阈值是 5 秒(系统权威、不可调);Argus 的 AppHang watchdog 阈值更敏感(700ms),能在系统弹出 ANR 对话框之前就捕获到主线程栈——大多数 ANR 在成为 ANR 之前,先是一次可观测的 AppHang。
defer-to-next-launch(崩溃落盘、重启上报)
崩溃发生的瞬间进程随时会死,SDK 只做一件事:把原始崩溃数据写入本地文件
(iOS Library/Caches/Argus/crashes/、Android <filesDir>/argus/crashes/)。
下次 Argus.init(...) / Argus.start(...) 时扫盘发现遗留文件,转成崩溃事件入队上报,
成功后删除。所以:崩溃事件出现在 Console 的时机是「用户下次打开 App」之后,而非崩溃当下。
面包屑(Breadcrumbs)
每次 Argus.log() / Argus.addBreadcrumb() 会记入端上环形缓冲,崩溃事件携带最近的
面包屑序列上报。崩溃详情页按「距崩溃的相对时间」渲染(-3s / -2m),还原用户崩溃前的操作轨迹。
符号化状态(symbolicate_status)
发布版二进制的崩溃栈是十六进制地址(iOS / Android)或压缩混淆后的 JS 栈,需要用符号文件 还原。每个崩溃事件带有符号化状态:
| 状态 | 含义 |
|---|---|
ok | 符号化成功,展示 函数名 at 文件:行号 |
partial | 部分帧成功(详情页标「部分已 symbolicate」徽标) |
no_symbol | 缺少匹配 build_id 的符号文件——详情页高亮提示并给出上传入口 |
pending | 已入队、符号化处理中 |
failed | 符号文件存在但解析失败 |
| 空 | 无需符号化(如可读的 JS / Kotlin 异常栈),直接展示原始栈 |
接入
平台捕获矩阵
| 端 | 自动捕获 | 快速开始 |
|---|---|---|
| iOS | NSException + POSIX signal + Mach exception 三保险;AppHang watchdog;OOM 启发式 | /quickstart/ios/ |
| Android | Thread.UncaughtExceptionHandler + sigaction 5 信号 + defer 落盘;AppHang watchdog;OOM(ApplicationExitInfo) | /quickstart/android/ |
| Web | window.onerror + unhandledrejection | /quickstart/web/ |
| Flutter | FlutterError.onError + PlatformDispatcher.instance.onError | /quickstart/flutter/ |
| React Native | ErrorUtils 全局 handler(同步异常,保留 red box);Hermes Promise rejection(opt-in captureUnhandledRejections) | /quickstart/react-native/ |
所有端默认开启自动捕获(captureUncaught: true),初始化即生效,无需额外代码。
不想自动捕获时显式传 captureUncaught: false。
手动上报已捕获的异常
捕获后想记录但不想让 App 崩溃的异常,用 captureCrash:
Web
try {
// ...
} catch (err) {
Argus.captureCrash({
errorType: err.constructor.name,
errorMessage: err.message,
stacktrace: err.stack,
attributes: { component: "checkout" }, // 可选
});
}给崩溃分组一个好版本号:初始化时设置 appVersion(如 "2.3.0"),Issue 才能按版本
下钻「这个崩溃是不是新版本引入的」。
符号化:上传 dSYM / .so / sourcemap
符号化管线基于 build_id 匹配:崩溃事件携带二进制镜像的 build_id,服务端在已上传的 符号文件中查找同 build_id 的文件完成解析。当前支持 iOS dSYM 与 Android .so (NT_GNU_BUILD_ID);Web sourcemap 与 Android ProGuard mapping 的解析为规划中 (Console 已可上传 sourcemap 备用)。
方式一:Console 手动上传
进入 Console → Settings → Symbols(/settings/symbols 页面):
选择平台
iOS (dSYM .zip) / Android (.so) / Web (sourcemap .map)。
选择文件并上传
iOS 打包 dSYM 目录为 zip 后上传;Android 上传带调试符号的 .so。上传结果会提示成功 /
被拒绝的文件数。
核对已上传符号
页面下方列出该项目已上传的符号文件;也可用 MCP 工具 symbol.list 检查某平台是否已有符号。
方式二:fastlane CI 自动上传(推荐)
用官方 fastlane 插件把符号上传固化进发版流水线,杜绝「忘了传 dSYM」:
# Gemfile(v0 暂以本地路径引用,RubyGems 发布规划中)
gem 'fastlane-plugin-argus', path: '/path/to/x-argus/sdks/fastlane-plugin-argus'# Fastfile
lane :upload_argus_symbols do
gym(scheme: "MyApp") # 触发 dSYM 生成
upload_argus_symbols(
dsn: ENV['ARGUS_DSN'], # argus://pk:sk@host/project-uuid
project_id: ENV['ARGUS_PROJECT_ID'],
dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH],
platform: 'ios',
)
end| 参数 | 环境变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
dsn | ARGUS_DSN | 是 | — | Argus DSN |
dsym_path | — | 是 | — | dSYM zip 文件路径 |
project_id | ARGUS_PROJECT_ID | 是 | — | 项目 UUID |
endpoint | — | 否 | https://symbolicator.argusplatform.com | Symbolicator 服务地址(自托管时改为自己的地址) |
platform | — | 否 | ios | ios 或 android |
插件源码见
sdks/fastlane-plugin-argus。
事后补传符号
崩溃先到、符号后传时,历史事件保持 no_symbol。补传符号后可用 MCP 工具
crash.symbolicate 按 event_id 手动重新触发符号化(异步执行,完成后用 crash.get
重取即可看到源码级堆栈)。
Console 使用
Issue 列表
进入 Console → Issues(/issues 页面),按 signature 聚合展示:
| 列 | 说明 |
|---|---|
| Error | error_type + 异常消息,点击进详情 |
| Signature | 16 位指纹 |
| Events | 该 Issue 的事件总数 |
| Users | 影响的去重用户数(未登录计为 anonymous) |
| First seen / Last seen | 首次 / 最近发生时间 |
Issue 详情
点击任一 Issue 进入详情页(/issues/<signature>),从上到下:
- 头部统计:事件数、影响用户数、首次 / 最近发生时间;最新事件带
trace_id时 提供「查看链路 trace」跳转; - Analyze with AI:一键把该崩溃交给 Platform Copilot 做根因分析 (自动携带 signature / error_type / app_version 上下文);
- Stacktrace:符号化成功时逐帧展示
函数名 at 文件:行号 [所属包],原始栈折叠可查;no_symbol时高亮提示并链接到符号上传页; - Affected events:每次发生的时间 / 用户 / 会话 / trace / 系统 / 版本, 用户与会话可点击跳到对应日志时间线;
- Breadcrumbs:最新事件携带的崩溃前操作轨迹(相对时间 + 级别 + 消息)。
SDK API
| API | 端 | 说明 |
|---|---|---|
captureUncaught(初始化配置) | 全端 | 自动捕获开关,默认 true |
Argus.captureCrash(...) | 全端 | 手动上报已捕获异常(errorType / errorMessage / stacktrace? / attributes?) |
Argus.addBreadcrumb(message, level) | iOS / Android | 手动记面包屑(不上报,崩溃时随事件携带) |
ArgusConfig.captureAnr(Android)/ captureAppHang(iOS) | 移动端 | AppHang watchdog 开关,默认 true |
ArgusConfig.captureOom | 移动端 | OOM 检测开关,默认 true |
appVersion(初始化配置) | 全端 | 版本号,用于按版本聚合与回归判断 |
MCP 工具
| 工具 | 用途 | 关键参数 |
|---|---|---|
crash.recent | 最近 N 条崩溃(附解析后的 breadcrumbs) | project_id、limit |
crash.get | 按 event_id 取单个崩溃事件 | project_id、event_id |
crash.by_signature | 某个 Issue(signature 组)的全部事件 | project_id、signature(16 位 hex)、limit |
crash.by_user | 某个用户的全部崩溃(「用户 X 为什么总在抱怨」) | project_id、user_id、limit |
crash.stats | 近 N 天崩溃统计:总量、按 signature 拆分、逐日趋势 | project_id、days、app_version |
crash.aggregate | 按 signature / error_type / device_os / app_version / user_id 分组计数 | project_id、group_by、limit |
crash.recurrence | 判定某 Issue 在指定时间后是否复发(验证修复是否生效,确定性判定) | project_id、signature、since(ISO 8601) |
crash.symbolicate | 手动(重新)触发某事件符号化(补传符号后回补历史栈) | event_id |
symbol.list | 列出项目已上传的符号文件,可按平台过滤 | project_id、platform(ios / android / web) |
配额与计费
- 1 个崩溃事件 = 1 个 event,计入 events 配额;
- Copilot 根因分析 = 1 次 AI Call(含其背后的多个工具调用);
- 符号文件本体存对象存储,单文件上限 500 MB。
详见 配额与计费。
常见问题
堆栈全是十六进制地址?
这是 no_symbol(缺符号文件)状态。上传该构建对应的 dSYM / .so(build_id 必须与崩溃时
加载的二进制一致),再对历史事件用 crash.symbolicate 触发回补。用 symbol.list 可先
确认该平台是否已有符号。符号与构建一一对应:每次发版都要上传当次构建的符号,
建议用 fastlane 插件固化进 CI。
触发了崩溃但 Console 里没有?
崩溃是 defer-to-next-launch 上报——再次启动 App 才会扫盘补报,稍等再刷新 /issues。
另外在 Xcode 调试器 attach 时 SIGSEGV 等信号会被 debugger 拦截,验证原生崩溃请脱离
调试器直接运行 App。
AppHang 和 ANR 是什么关系?在哪里看?
AppHang 是 Argus watchdog 检测的主线程阻塞(默认 ≥ 700ms,附卡顿时的主线程栈);
ANR 是 Android 系统级判定(5 秒)。AppHang 事件走崩溃通道,在 /issues 里以
error_type = AppHang 出现,按卡顿栈聚合,hang_duration_ms 记录阻塞时长。
怎么验证接入成功?
在测试构建里主动抛一个未捕获异常(或调用 Argus.captureCrash 上报一条测试事件),
重启 App 后到 /issues 确认出现对应 Issue。示例工程
(examples/android-demo /
examples/ios-demo)
内置了触发 Kotlin 异常、JNI SIGSEGV、OOM 的按钮可供参考。
同一个 bug 为什么偶尔被分成多个 Issue?
指纹按「栈顶应用帧 + 异常类型 + 归一化消息」计算。如果异常消息里包含高度动态的内容
(随机 ID、完整 URL),或崩溃发生在不同调用路径的不同栈顶,会得到不同 signature。
让异常消息保持稳定、把动态值放进 attributes 而非 message,聚合会更准。
更多排障见 故障排查。