Crash
Android Crash 指的是应用在运行过程中发生未捕获异常或致命错误,导致进程被系统终止的现象。它会直接影响用户体验,甚至造成用户流失,是 Android 开发和运维中必须重点解决的问题。
Android Crash 的分类与常见原因
根据崩溃的触发层级和原因,可分为 Java/Kotlin 层 Crash、Native 层 Crash 和 ANR(Application Not Responding) 三类,其中 ANR 虽不属于严格意义的 Crash,但表现和影响与 Crash 类似。
1. Java/Kotlin 层 Crash
由 Java/Kotlin 代码的未捕获异常引发,可通过 try-catch 捕获,也是最常见的崩溃类型。
- 常见原因
- 空指针异常(NullPointerException):调用了
null对象的方法或属性(最高频原因)。 - 数组越界(ArrayIndexOutOfBoundsException):访问数组下标超出范围。
- 类型转换异常(ClassCastException):强制类型转换失败(如
String转TextView)。 - 资源未找到(ResourceNotFoundException):引用了不存在的资源 ID(如布局、图片)。
- 并发异常:如
ConcurrentModificationException(迭代集合时修改元素)。 - 权限异常:未申请权限就调用相关 API(如 Android 6.0+ 危险权限)。
- 空指针异常(NullPointerException):调用了
2. Native 层 Crash
由 C/C++ 代码引发的崩溃,通常是 JNI 调用错误或底层库问题,堆栈信息复杂,定位难度高。
- 常见原因
- JNI 调用时类型不匹配(如 Java
int与 Nativelong混用)。 - 访问非法内存地址(如空指针、野指针)。
- 底层库(如
so库)版本不兼容或编译错误。 - 内存泄漏导致内存耗尽(OOM)。
- JNI 调用时类型不匹配(如 Java
3. ANR(应用无响应)
ANR :应用主线程(UI 线程)阻塞超过阈值(输入事件 5s,广播 10s,服务 20s),系统弹出 ANR 对话框。
- 常见原因
- 主线程执行耗时操作(如网络请求、数据库读写、复杂计算)。
- 主线程被死锁阻塞。
- 大量耗时任务抢占主线程资源(如频繁的
Handler消息发送)。
Android Crash 的监控方案
监控的核心目标是 捕获崩溃信息、上报到服务端、统计崩溃率,常用方案分为 自研监控 和 第三方平台 两类。
1. 自研监控方案
(1)Java 层 Crash 监控:UncaughtExceptionHandler
Android 提供了 Thread.UncaughtExceptionHandler 接口,可全局捕获未处理的 Java 异常。
- 核心原理
- 实现
UncaughtExceptionHandler接口,重写uncaughtException方法,在方法中收集崩溃信息。 - 通过
Thread.setDefaultUncaughtExceptionHandler设置全局异常处理器。
- 实现
- 关键信息收集
- 崩溃堆栈(
Throwable.getStackTrace()) - 设备信息(机型、系统版本、SDK 版本)
- 应用信息(版本号、包名)
- 用户行为(如崩溃前的操作步骤)
- 崩溃堆栈(
- 代码示例
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static CrashHandler instance = new CrashHandler();
private Thread.UncaughtExceptionHandler mDefaultHandler;
private CrashHandler() {
// 获取系统默认的异常处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
public static CrashHandler getInstance() {
return instance;
}
public void init() {
// 设置当前类为全局异常处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
// 1. 收集崩溃信息
collectCrashInfo(e);
// 2. 保存到本地文件(防止上报失败)
saveCrashInfoToFile(e);
// 3. 上报到服务端
uploadCrashInfo();
// 4. 交给系统默认处理器(可选,会弹出崩溃对话框)
if (mDefaultHandler != null) {
mDefaultHandler.uncaughtException(t, e);
}
}
private void collectCrashInfo(Throwable e) {
// 收集设备、应用、堆栈信息
}
private void saveCrashInfoToFile(Throwable e) {
// 保存到本地 SD 卡或内部存储
}
private void uploadCrashInfo() {
// 上传到后端服务器
}
}
(2)Native 层 Crash 监控
Native 层崩溃无法通过 Java 层的异常处理器捕获,需借助 信号量机制 和 第三方库。
- 核心原理:Linux 系统中,Native 崩溃会触发信号(如
SIGSEGV段错误、SIGABRT中止信号),通过注册信号处理器捕获崩溃信息。 - 常用工具
- Breakpad:Google 开源的跨平台崩溃捕获库,可生成
minidump崩溃文件,结合符号表解析堆栈。 - Android NDK 的
ndk-stack工具:用于解析 Native 崩溃的堆栈日志(需要so库的符号表)。
- Breakpad:Google 开源的跨平台崩溃捕获库,可生成
(3)ANR 监控
ANR 的监控比普通 Crash 复杂,核心是监控主线程阻塞状态。
方案 1:利用系统日志
系统发生 ANR 时,会在
/data/anr/traces.txt文件中写入详细日志(主线程堆栈、CPU 使用率等)。可通过定时读取该文件,检测 ANR 并上报。- 缺点:需要
root权限,非 root 设备无法直接读取。
- 缺点:需要
方案 2:自研监控(主线程 WatchDog)
启动一个独立的监控线程,定期向主线程发送心跳消息,如果主线程在指定时间内未响应,则判定为 ANR。
- 核心类:
Handler+ 子线程定时发送消息。
- 核心类:
2. 第三方监控平台
自研监控成本高,中小型应用推荐使用成熟的第三方平台,功能更全面且无需维护。
| 平台 | 核心优势 | 支持的崩溃类型 |
|---|---|---|
| Firebase Crashlytics | Google 官方出品,免费,集成简单,支持实时上报 | Java/Kotlin、Native、ANR |
| Bugly(腾讯) | 国内常用,支持多平台,提供崩溃分析、用户行为回溯 | Java/Kotlin、Native、ANR |
| 友盟 U-App | 结合用户画像,支持崩溃归因,适合运营分析 | Java/Kotlin、Native |
| Sentry | 开源可私有化部署,支持跨端(Android、iOS、Web),实时告警 | 全类型崩溃 |
Android Crash 的分析流程
捕获到崩溃信息后,需要定位根因并修复问题,通用分析流程如下:
1. 收集崩溃上下文信息
分析前必须获取完整的信息,否则难以定位问题:
- 基础信息:崩溃堆栈、设备型号、系统版本、应用版本、崩溃时间。
- 上下文信息:用户操作步骤、网络状态(Wi-Fi/4G)、是否低内存、是否有其他应用干扰。
- 扩展信息:ANR 需结合 CPU 使用率、主线程堆栈;Native 崩溃需结合
so库版本和符号表。
2. 定位崩溃根因
(1)Java 层 Crash 分析
直接查看崩溃堆栈,最顶层的堆栈行就是崩溃发生的代码位置。
- 示例:空指针异常堆栈
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
at com.example.myapp.MainActivity.onCreate(MainActivity.java:20)
at android.app.Activity.performCreate(Activity.java:8198)
- 定位:第 20 行的
TextView对象为null,未初始化就调用setText。
(2)Native 层 Crash 分析
Native 崩溃的堆栈是内存地址,需要通过符号表将地址转换为具体的代码行。
关键步骤
保留崩溃版本对应的
so库和符号表(编译时生成的.sym文件)。使用
ndk-stack工具解析:ndk-stack -sym /path/to/so/dir -dump /path/to/crash/log.txt解析后的堆栈会显示具体的 C/C++ 函数和行号。
(3)ANR 分析
重点查看 traces.txt 中的 主线程堆栈,找到阻塞主线程的耗时操作。
- 示例:主线程执行网络请求导致 ANR
"main" prio=5 tid=1 Waiting
| group="main" sCount=1 dsCount=0 flags=1 obj=0x72a092a0 self=0xb4000070f1d80000
| sysTid=1234 nice=0 cgrp=default sched=0/0 handle=0x779f4a4540
| state=S schedstat=( 1234567 8765432 123 ) utm=100 stm=20 core=1 HZ=100
| stack=0x7fd836a000-0x7fd836c000 stackSize=8192KB
| held mutexes=
at java.net.PlainSocketImpl.socketConnect(Native method)
- waiting on a blocking call
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:387)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436)
at java.net.Socket.connect(Socket.java:621)
at com.example.myapp.MainActivity.fetchData(MainActivity.java:50)
at com.example.myapp.MainActivity.onCreate(MainActivity.java:25)
- 定位:第 50 行的
fetchData方法在主线程执行网络请求,阻塞超过 5s。
3. 复现与验证
定位根因后,需要复现崩溃场景,验证修复方案是否有效:
- 复现技巧:模拟相同的设备、系统版本、操作步骤;对于偶现崩溃,可增加日志或使用调试工具(如 Android Studio Profiler)。
- 修复原则:针对根因修复,而非掩盖现象(如空指针不能只加
null判断,要分析为何为空)。
Android Crash 的预防措施
最好的监控是预防,通过规范开发流程减少 Crash 发生:
- 严格的代码评审:重点检查空指针、类型转换、数组越界等高频问题。
- 主线程禁止耗时操作:网络请求、数据库读写、文件操作等必须放在子线程(使用
Coroutine、RxJava或AsyncTask)。 - 合理处理权限:Android 6.0+ 危险权限需动态申请,避免权限缺失导致崩溃。
- 使用安全的 API:如使用
String.valueOf()代替直接调用toString(),避免空指针。 - 内存优化:避免内存泄漏(如
Handler持有 Activity 引用、静态变量持有上下文),使用LeakCanary检测内存泄漏。 - 测试覆盖:多机型、多系统版本测试;使用 Monkey 测试进行随机压力测试,发现偶现崩溃。
关键工具推荐
- LeakCanary:Square 开源的内存泄漏检测工具,可自动检测并上报内存泄漏。
- Android Studio Profiler:监控应用的 CPU、内存、网络使用情况,定位性能问题和崩溃诱因。
- Logcat:系统日志工具,实时查看应用运行日志和崩溃堆栈。
- ndk-stack:解析 Native 崩溃堆栈的官方工具。
Android Crash 排查速查清单
这份清单涵盖 Java/Kotlin 层、Native 层、ANR 三类崩溃的全流程排查步骤,可直接用于开发和测试阶段的问题定位。
前期准备:必备信息与工具
| 类别 | 关键内容 |
|---|---|
| 必须收集的崩溃信息 | 1. 崩溃堆栈(完整调用链) 2. 设备信息(机型、系统版本、SDK 版本) 3. 应用信息(版本号、包名、渠道) 4. 上下文信息(用户操作步骤、网络状态、内存占用) 5. 崩溃时间与频次(偶现 / 必现) |
| 必备工具 | 1. 基础工具:Android Studio Logcat、Profiler 2. Java 层:自定义 CrashHandler、Bugly/Firebase 后台3. Native 层:ndk-stack、Breakpad、符号表( .sym 文件)4. ANR 专用: traces.txt 读取工具、WatchDog 监控脚本5. 辅助工具:LeakCanary(内存泄漏检测) |
分类型排查步骤
1. Java/Kotlin 层 Crash(最高频)
| 步骤 | 操作要点 |
|---|---|
| 1. 定位崩溃行 | 直接查看堆栈最顶层行,格式:类名.方法名(文件名:行号)例:MainActivity.onCreate(MainActivity.java:20) → 第 20 行代码问题 |
| 2. 识别异常类型 | 高频异常及解决方向: - 空指针(NPE):检查对象是否初始化、是否为 null- 数组越界:检查下标范围是否小于数组长度 - 类型转换失败:确认对象实际类型与强转类型是否匹配 - 资源未找到:检查资源 ID 是否写错、是否存在于对应 res 目录 |
| 3. 复现验证 | 1. 模拟相同设备 + 系统版本 + 应用版本 2. 复现用户操作步骤 3. 若偶现:增加日志打印关键变量值 |
| 4. 修复原则 | 不掩盖问题:如 NPE 不能只加 if (obj != null),要分析对象为何为空 |
2. Native 层 Crash(难度高)
| 步骤 | 操作要点 |
|---|---|
| 1. 准备前提条件 | 必须保留崩溃版本对应的 so 库和编译生成的符号表 |
| 2. 解析堆栈地址 | 使用 ndk-stack 工具,命令示例:ndk-stack -sym /你的so库目录 -dump /崩溃日志文件.txt |
| 3. 定位问题原因 | 常见根因及解决: - 非法内存访问:检查 JNI 层指针是否为空、是否越界 - 类型不匹配:核对 Java 类型与 JNI 类型映射(如 int ↔ jint)- so 库版本兼容:确认 so 库是否适配当前 CPU 架构(armeabi-v7a/arm64-v8a) |
| 4. 验证修复 | 替换修复后的 so 库,重新打包测试 |
3. ANR(应用无响应)
| 步骤 | 操作要点 |
|---|---|
| 1. 获取 ANR 日志 | 方式 1:root 设备直接读取 /data/anr/traces.txt 方式 2:通过第三方平台(Bugly)获取上报的 traces 日志 方式 3:自研 WatchDog 监控主线程阻塞状态 |
| 2. 分析主线程堆栈 | 重点看 main 线程的状态:- Waiting/Blocked:线程被锁阻塞,检查死锁- Running:执行耗时操作(网络 / 数据库 / 计算) |
| 3. 定位耗时代码 | 常见问题:主线程执行网络请求、数据库批量读写、复杂 UI 绘制 解决方向:将耗时操作移到子线程(Coroutine/RxJava) |
| 4. 优化验证 | 使用 Android Studio Profiler 监控主线程 CPU 占用率,确认耗时操作已迁移 |
通用修复与预防措施
| 阶段 | 具体操作 |
|---|---|
| 修复后验证 | 1. 必现崩溃:修复后需 100% 复现验证 2. 偶现崩溃:增加压力测试(如 Monkey 测试) 3. 回归测试:检查修复是否引入新问题 |
| 长期预防 | 1. 代码规范:避免主线程耗时操作 - 空指针防御(String.valueOf() 代替 toString()) - 动态权限申请(Android 6.0+)2. 监控体系:接入第三方崩溃监控平台 - 集成 LeakCanary 检测内存泄漏 3. 测试覆盖:多机型 / 多系统版本测试 - 灰度发布,提前发现问题 |
紧急排查技巧(线上崩溃)
- 优先看崩溃频次:高频崩溃需优先修复,影响大量用户
- 筛选关键设备:若仅特定机型崩溃,排查机型适配问题(如刘海屏 / 系统定制 ROM)
- 对比版本差异:若某版本突然新增崩溃,对比代码提交记录,定位新增代码