第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 以后才有的,它只输出崩溃日志,非常干净。我经常先用这个看看有没有明显的堆栈,然后再用完整日志深入分析。
-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 往往就是孤证了。
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 用来抓第一手日志,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 → 然后才去分析。这样你手里有两份数据,互相印证,定位效率高很多。