二、SystemUI 常见崩溃类型

SystemUI 崩溃,说白了就那么几种“死法”。我这些年排查过的 case,十有八九都能归到下面这几类里。咱们把它掰开揉碎看一看,往后你碰到类似崩溃 log,一眼就能知道问题大概出在哪儿。

1. NullPointerException —— 空指针,最常见的噩梦

嗯,这是榜首。SystemUI 里到处都是回调、异步数据、跨进程 binder 调用,只要有一个对象没初始化好就被拿来用,立马崩给你看。

我个人习惯:先看崩溃堆栈第一行 —— at com.android.systemui.xxx.onClick 这种,八成就是某个 view 或者 listener 是 null。我记得有一次排查「下拉快速设置闪退」,堆栈指向了 QSPanelController.onDataChanged,最后发现是 mHost 在 fragment 重建时没绑上。

调试技巧:抓 logcat 时加上 -b all,同时看 System.err 和 ActivityManager 的 ANR in …。空指针往往不是根因,它只是结果。

2. ClassCastException —— 类型转换翻车

这个在插件化或动态加载场景下特别爱出现。比如你从 getSystemService() 拿了一个对象,强转为 SomeManager,结果实际是另一个版本的类型。

我曾经被坑过一次:升级 Android 13 后,BluetoothManager 的内部实现类变了,但旧代码里硬转成 BluetoothAdapter,直接 ClassCastException。你想想看,这类崩溃最难找的就是「代码里看着都对,运行时就是不对」。

3. IndexOutOfBoundsException —— 集合越界

通知列表、快捷开关列表、任务列表……SystemUI 到处都是 ArrayList 和 RecyclerView。多线程环境下,一边在读一边在删,索引瞬间就飞了。

我记得有个 bug 是「屏幕旋转时通知栏闪退」,堆栈指向 NotificationPanelView.onLayout 里 mChildren.get(i)。最终定位是 mNotificationList 被两个线程同时修改,没有加锁。

⚠️ 注意:不要在 onLayout / onMeasure 里做集合增删操作,那是 UI 线程的雷区。如果非要改,用 List#subList 快照或者 CopyOnWriteArrayList。

4. IllegalStateException —— 状态不对崩

这类崩溃常见于「流程走错了」。比如 Fragment 的 commit() 发生在 onSaveInstanceState 之后,或者 MediaSession 回调时界面已经 detach。

我碰到过一个案例:锁屏上音乐控制器点击下一首,崩溃堆栈全是 IllegalStateException: Can not perform this action after onSaveInstanceState。你看,就是 MediaController.Callback 回到主线程后,Activity 已经暂停了。

5. Resources$NotFoundException —— 资源找不到

这种通常在 overlay 或者主题切换时冒出来。SystemUI 大量使用 @*android:drawable/xxx 引用系统资源,一旦 overlay 没有跟随版本更新,引用的资源 ID 就失效了。

我个人建议:如果崩溃堆栈里有 Resources.getValue 或 TypedArray.getDrawable,优先检查 overlay 的 AndroidManifest.xml 里 targetPackage 和 targetSdkVersion 是不是最新的。

常见崩溃类型速查表

崩溃类型 典型原因 调试关键点
NullPointerException 对象未初始化、回调时序错乱 看调用链上哪个变量是 null
ClassCastException 类型强转与运行时类型不匹配 instanceof 检查 + 日志输出实际类型
IndexOutOfBoundsException 多线程集合操作、索引计算错误 加断点查集合 size 和 index
IllegalStateException 生命周期状态断片、重复 commit 检查 isAdded()、isResumed()
Resources$NotFoundException overlay 资源过期、ID 冲突 apktool d 反编译查看 overlay 资源
SystemUI 常见崩溃类型归纳 SystemUI 崩溃 NullPointerException ClassCastException IndexOutOfBounds IllegalStateException Resources$NotFound 调试核心思路 堆栈第一行 → 定位异常类型 → 回溯调用链 结合 logcat 上下文中的关键变量值 注意:实际 crash 可能是多种类型混合,需要分层剥离

实际调试怎么用这张图?

你拿到一个 tombstones 或 log,先看 Exception 名称,对应到图上的分支。然后:

  • 如果是 空指针,重点查近期新增的异步代码、lambda 回调。
  • 如果是 类型转换,想办法在 crash 前用 getClass().getName() 打印实际类型。
  • 如果 越界,加断点看集合的 size() 和操作的索引,八成是多线程没同步。
  • 如果 IllegalState,先检查 Fragment/Activity 的生命周期状态 —— isAdded() 经常被忽略。
  • 如果 Resources not found,直接 adb shell pm list packages -f 看有没有被覆盖的 overlay apk。

个人结论:SystemUI 的崩溃复现率往往不高,但正因如此,类型定位越准确,复现思路越清晰。我调试时习惯先把崩溃类型圈定到三分,再根据调用栈里核心类的名称缩小范围 —— 比如看到 com.android.systemui.statusbar.notification 打头,就优先查通知相关的对象和回调。