Crash 产生原因分析
聊完了 Crash 和 ANR 的区别,咱们得扎进具体的 Crash 种类里看看。说实话,我干了这么多年 Android 开发,遇到过最多、最让人头疼的就是那几种老面孔。今天咱们就把它们挨个掰开揉碎,看看背后到底什么鬼,以及怎么从源头堵住它们。
NullPointerException —— 最熟悉的陌生人
说实话,NPE 在 Crash 排行榜上常年霸占第一。我个人习惯在写代码时只要看到可能为 null 的对象,就下意识加个判空。但你想想看,很多同学明明知道这个道理,代码里还是经常 “嘭” 一下炸掉。
为什么会这样?归根结底就两种场景:
- 链式调用中断了——比如
user?.getAddress()?.getCity(),如果你用了 Java 而不是 Kotlin,中间任何一个环节返回 null,直接就 NPE。 - 第三方 SDK 返回空数据未处理——我记得有一次做 IM 聊天,对方服务器偶尔返回一个空的 profile 对象,结果我们没判空直接拿头像 URL 去加载图片,线上瞬间爆了一堆 NPE。
Optional 或 @Nullable 注解都能帮你提前发现风险。
一个典型例子:
// 不安全的写法
String city = user.getAddress().getCity();
// 如果 user 或 getAddress() 为 null → 崩
// 安全的写法
if (user != null && user.getAddress() != null) {
String city = user.getAddress().getCity();
}
IndexOutOfBoundsException —— 下标玩脱了
这个异常我也踩过不少坑。说白了就是对列表、数组的访问越界。最常见的就是遍历时删除了元素,或者从网络拿到的数据数量和预想的不一致。
我一个项目里遇到过这个 bug:列表有 10 项,后台列表新增了一个隐藏项(用户不可见),但前端分页时按之前 10 项的大小取数据,结果 list.get(10) 直接越界。后来我给所有集合操作都加了边界判断。
避免越界的几个原则:
- 用增强 for 循环或
for (int i = 0; i < list.size(); i++)时,循环条件一定要基于size()动态计算。 - 执行
list.get(index)前,确认index >= 0 && index < list.size()。 - 使用
List.subList()后,不建议对原列表做结构性修改(如 add/remove),极易触发ConcurrentModificationException或者 IndexOutOfBoundsException。
RecyclerView.Adapter 的 onBindViewHolder 中直接 get(position) 而不过滤,特别是在列表有 header/footer 或者局部刷新时,position 很容易与实际数据源索引对不上。
ClassCastException —— 类型强转的雷
这个异常说白了就是你想把一只猫当狗用。强转时类型不匹配,运行时直接 Crash。尤其是在使用 ArrayList 转 MyModel 时,或者从 JSON 反序列化得到的对象类型与预期不一致。
我的一个惨痛经历:早期用 Gson 解析接口,后端返回的某个字段有时是 JSONObject 有时是 JSONArray。我没加 instanceof 判断直接强转成 List,结果线上部分用户数据直接崩溃。后来加了类型校验才解决。
正确的做法:
Object data = response.get("data");
if (data instanceof List) {
List<MyModel> list = (List<MyModel>) data;
// 处理列表
} else if (data instanceof Map) {
MyModel model = new MyModel((Map) data);
// 处理单条
} else {
// 兜底处理
}
另外,泛型擦除也是个隐形杀手。你在代码里写了 List<String>,编译后变成 List,运行时拿到的元素类型未检查就直接强转,很容易翻车。所以从外部(比如 Intent 传递的 Serializable、Parcelable)取数据时,一定要做类型校验。
总结表格:三类常见 Crash 对比
| Crash 类型 | 典型触发场景 | 根因 | 推荐预防手段 |
|---|---|---|---|
| NullPointerException | 未判空调用方法/访问属性 | 对象引用为 null | 判空检查、Optional、注解 |
| IndexOutOfBoundsException | 数组/列表下标越界 | index ≥ size 或 index < 0 | 边界判断、使用增强 for |
| ClassCastException | 不安全的向下转型或泛型误用 | 运行时类型不匹配 | instanceof 检查、泛型校验 |
核心思路:这三种 Crash 其实都有很清晰的触发模式。我们只要能识别出“可疑代码段”,加一道防御判断,就能拦截大部分线上问题。顺便说一句,我们还可以在关键路径上埋点,用 Crash 采集工具把堆栈信息实时上报,这样即使漏网了也能快速定位。