第4章 崩溃日志捕获:用 adb logcat 抓完整日志,用 bugreport 生成诊断报告

做 SystemUI 调试,第一件事就是拿到崩溃日志。没有日志,就像医生没有检查报告,你只能瞎猜。我个人习惯是,一旦遇到崩溃,先别急着重启手机——先保住现场。

这一章我们来聊聊怎么优雅地抓日志。不是那种只会按一个按钮就跑的抓法,而是能抓到真正有用的、完整的、带时间线的东西。

4.1 为什么日志经常不完整?

你在 bug 上报里经常看到这样的反馈:「SystemUI 闪退了,但我只看到一行日志」。为啥?因为默认的 logcat 缓冲区很小,大概 64KB,稍微跑几个动画、刷几帧界面,之前的日志就被冲掉了。SystemUI 的崩溃又往往发生在复杂交互之后,日志早被覆盖了。

还有一个坑:手机重启后,logcat 缓冲区会清空。如果你崩溃后直接按电源键重启,对不起,什么都查不到。所以第一反应应该是——拔掉 USB 线?不对,是赶紧插上 USB 线抓日志!

4.2 使用 adb logcat 抓取完整日志

4.2.1 基础命令

adb logcat

这是最原始的命令,但直接跑它会输出所有进程的日志,内容太多,刷屏很快。SystemUI 的进程名是 com.android.systemui,我们可以用 -s 或 --pid 来过滤。

按进程名过滤:

adb logcat -s "SystemUI"

等等,这命令有个问题。它只匹配 SystemUI 这个 tag,但 SystemUI 内部很多日志 tag 是 SystemUIService、StatusBar、NotificationListener 等。更好的办法是用 --pid。

先找到 SystemUI 的 PID:

adb shell ps | grep systemui

然后:

adb logcat --pid=<PID>

但 PID 每次重启会变,所以我一般写个脚本自动抓取。如果你只想看崩溃相关的日志,可以加上 Crash 或者 Fatal 关键字:

adb logcat -b crash

这个 -b crash 选项是 Android 7.0 以后才有的,它只输出崩溃日志,非常干净。我经常先用这个看看有没有明显的堆栈,然后再用完整日志深入分析。

小技巧: 如果你想抓一长段日志(比如重现 crash 的过程),记得加 -v threadtime 显示精确时间和线程号。这样你在 bugreport 里也能对得上时间线。
adb logcat -v threadtime -b all > crash_log.txt

4.2.2 调整日志缓冲区大小

默认的 logcat 缓冲区太小,对调试 SystemUI 来说不够。我建议在开发机上把缓冲区调大,这样崩溃前的日志能保留更久。

查看当前缓冲区大小:

adb logcat -g

输出类似:

main: 256K, system: 256K, crash: 256K

调大缓冲区的方法有两种:

  • 临时修改: 通过 adb shell setprop logcat.large.logs true,但重启后失效。
  • 永久修改: 在 build.prop 中添加 ro.logd.size=4M,需要 root 权限。我一般在编译 userdebug 版本时直接加在 BoardConfig.mk 里。

4.2.3 抓取 SystemUI 特定 Tag 的日志

SystemUI 里的日志 tag 非常多,下面是几个常用的:

Tag 说明 常见场景
SystemUI 顶级 tag,很多地方会用 初始化、生命周期
StatusBar 状态栏相关 图标更新、点击事件
NotificationListener 通知监听器 通知显示、拉取
QuickSettings 快捷设置面板 面板展开、Tile 行为
BatteryController 电池状态 电量变更、充电状态
KeyguardViewMediator 锁屏管理 锁屏显示、解锁

你可以组合多个 tag 来过滤:

adb logcat -s "SystemUI:V" "StatusBar:V" "QuickSettings:V" -v threadtime

4.3 使用 bugreport 生成系统诊断报告

光有 logcat 还不够。SystemUI 崩溃经常跟系统服务状态、内存快照、进程信息有关。bugreport 是一个超级 dump,它把 dumpsys、dumpstate、logcat 全部打包成一个 zip 文件。

4.3.1 生成 bugreport

adb bugreport ./bugreport.zip

这个命令会等待几秒钟到一两分钟,然后在你当前目录下生成一个 bugreport_YYYY-MM-DD-HH-MM-SS.zip 文件。里面包含:

  • bugreport.txt — 纯文本报告,内容非常全
  • logcat 等日志文件
  • dumpsys 输出(包括 SystemUI 相关的服务)
  • dumpstate 信息(进程列表、线程堆栈等)
  • screenshot.png — 生成报告时的屏幕截图(如果有)

我个人习惯在复现 crash 之后,立马跑一次 bugreport,这样能拿到最完整的现场。特别是那些偶现的 crash,这一次 report 往往就是孤证了。

注意: bugreport 在手机上运行时,如果内存很紧张或者系统已经处于不稳定状态,可能会失败。如果 adb bugreport 卡住了,可以先试试 adb shell dumpsys 手动抓一些关键服务。

4.3.2 从 bugreport 中定位 SystemUI 问题

解压后打开 bugreport.txt,我一般先搜索这几个关键字:

  • ANR in: com.android.systemui — 应用无响应的记录
  • Crash log for process: com.android.systemui — 崩溃堆栈
  • FATAL EXCEPTION — 未捕获异常
  • WATCHDOG — 系统 watchdog 检测到异常

找 crash 日志时,我通常按时间倒序查找,因为最近的崩溃在报告末尾。你也可以用 grep:

grep -n "SystemUI" bugreport.txt | grep -i "crash\|fatal\|exception"

还有一个很有用的 section 是 Dumpsys activity services,这里面列出了 SystemUI 中所有正在运行的 Service 及其状态。如果某个 Service 发生了死锁或者绑定超时,你会看到 app waiters 或者 blocked 信息。

4.3.3 对比 logcat 与 bugreport 中的同一事件

有时候你在 logcat 里看到一行崩溃日志,但是在 bugreport 里找不到完全相同的堆栈。这是因为 bugreport 包含了多轮 dump,时间点可能早于或晚于崩溃瞬间。

我的做法是:先用 logcat 精确找到崩溃时刻的日志头(比如 Process: com.android.systemui 那一行),然后记录时间戳,再到 bugreport 里搜索这个时间点附近的日志。bugreport 中的 logcat 文件通常按时间排序,很容易对应上。

4.4 本章知识结构

下面这张图总结了崩溃日志的捕获流程与工具选择,你可以用它对照自己的调试步骤:

崩溃日志捕获与诊断流程 系统发生崩溃 adb logcat 抓取 实时日志(过滤 SystemUI) adb bugreport 生成系统诊断报告 保存现场 不要重启!先插线 分析日志 → 定位崩溃原因 查找 Fatal Exception 堆栈+行号 检查系统服务 dumpsys activity services 查看 ANR / Watchdog 时间线对齐

核心思想: 崩溃发生后,adb logcat 用来抓第一手日志,bugreport 用来拿完整 dump。两个工具配合使用,才能得到崩溃的全貌。千万别只用其中一个。

4.5 避坑指南

  • 不要忘记加 -b all: 有些崩溃日志在 main 缓冲区,有些在 system 缓冲区,还有些在 crash 缓冲区。抓一遍所有缓冲区最保险。
  • 小心日志被覆盖: 如果手机在崩溃后自动重启,logcat 缓冲区会被清空。一定要在重启前拔插 USB 吗?不对,要在重启前用 adb 抓到日志。但很多场景下手机自己重启了,这时候你只能用 bugreport(如果它之前已经开启了)或者查看 /data/system/dropbox 里的 tombstone 文件。
  • 我曾经碰到过一个坑: 某次 SystemUI 崩溃后,手机黑屏了,但 adb 还能连。我当时没在意,直接拔线重启,结果再也抓不到那个 crash 的堆栈了。后来才学会,先用 adb shell logcat -d 把当前缓冲区的内容倒出来,再 reboot。嗯,这个习惯我一直保持到现在。

4.6 本章小结

其实抓日志就两招:一个是 adb logcat,一个是 bugreport。但很多人只会在 crash 后跑一次 logcat,拿到几行日志就觉得够了。SystemUI 的崩溃往往牵涉到多个模块的交互,你想想看,如果只是看到了 NullPointerException 一行,你知道是哪个对象的哪个引用吗?

所以,我建议你记住这个流程:发现崩溃 → 立刻插 USB → 先跑一次完整的 adb logcat -b all -v threadtime > log.txt → 再跑 adb bugreport → 然后才去分析。这样你手里有两份数据,互相印证,定位效率高很多。