3. 日志系统基础:logcat命令详解、日志级别、Tag过滤、日志缓冲区管理与持久化
日志,说白了就是 SystemUI 崩溃时的第一道防线。你想想看,一个异常堆栈抛出来,如果连日志都没抓到,那跟睁眼瞎有啥区别?我个人习惯是,每次调试 SystemUI 之前,先打开 logcat 窗口,手指悬在键盘上——这和医生开机就得看心电监护仪一个道理。
3.1 日志级别与 Tag——给日志贴标签、分等级
Android 日志有 6 个级别,从低到高分别是:
| 级别 | 值 | 含义 | 常见用法 |
|---|---|---|---|
| VERBOSE | V | 叨叨 | 开发阶段全输出,发布要关掉 |
| DEBUG | D | 调试 | 比如状态栏布局变化时的中间信息 |
| INFO | I | 普通 | 启动、配置加载等关键节点 |
| WARN | W | 警告 | 资源紧张、预期外的分支 |
| ERROR | E | 错误 | 崩溃前的业务逻辑错误 |
| FATAL | F | 致命 | 进程必死,看到这级别基本玩完 |
Tag 是什么?你可以把它理解成日志的「科室名」。SystemUI 内部有几十个 Tag,随便列几个常用的:
SystemUI—— 顶层大总管StatusBar—— 状态栏专属NotificationListener—— 通知监听KeyguardViewMediator—— 锁屏核心RecentsPanelView—— 最近任务QuickSettings—— 快捷设置面板
我在项目中遇到过这样一个坑:一条关键的错误日志死活看不到,后来发现是另一个进程刷了无数条 V 级别日志,把缓冲区挤爆了。嗯,从那以后我每次定位问题都先看一眼“日志洪水”到底是谁在发。
3.2 logcat 命令详解——调试员的瑞士军刀
基本用法谁都会,但真正用好 logcat 还需要知道一些关键选项。我列个表,你直接收藏就行:
| 选项 | 说明 | 典型用法 |
|---|---|---|
-v <format> | 输出格式 | threadtime(最常用,含线程信息)brief(短格式)long(完整元数据) |
-s | 静默模式,仅显示指定 Tag | adb logcat -s SystemUI:V *:S |
-b <buffer> | 指定缓冲区 | main system events crash radio多个用逗号, adb logcat -b all |
-c | 清空缓冲区 | 调试前先清空,避免干扰 |
-g | 查看缓冲区大小和剩余 | adb logcat -g |
-f | 输出到文件 | adb logcat -f /data/local/tmp/log.txt |
-d | 一次性输出并退出 | 配合 grep 可在非 root 设备使用 |
我最常用的组合拳:
adb logcat -v threadtime -b all -s SystemUI:V *:S
这个命令的意思是:按线程时间格式输出,从所有缓冲区捞日志,只显示 Tag 为 SystemUI 的内容,其他 Tag 全部静默。你想想看,这样就不会被其他进程的噪音打扰了。
还有一个更粗暴的:直接打日志到本地文件,然后慢慢分析。
adb logcat -v threadtime -b all -f /data/local/tmp/sysui_crash.log
adb logcat -s "SystemUI|KeyguardViewMediator" 这种正则匹配。不过要注意,Android 原生的 -s 不支持通配符,但你可以用 grep -E 代替,比如:adb logcat | grep -E "SystemUI|KeyguardViewMediator|StatusBar"
3.3 日志缓冲区管理——别让日志凭空消失
Android 的日志不是直接往文件写的,而是通过 logd 守护进程丢到一个环形缓冲区。我见过很多新手朋友跑半天 logcat ,发现关键日志没了,其实是被后来的日志覆盖了。
缓冲区有这几个:
- main:普通应用日志(Log.d/v/i/w/e 默认写到这里)
- system:系统级日志(一般不用,除非你明确指定)
- events:事件日志(ANR、输入事件、开机过程等)
- crash:崩溃日志(native crash、tombstone)
- radio:RIL 相关(电话、网络)
- kernel:内核日志(需 root 或 eng 版本)
怎么看当前缓冲区情况?
$ adb logcat -g
main: ring buffer is 256 Kb (151 Kb consumed, 104 Kb free)
system: ring buffer is 256 Kb (0 Kb consumed, 256 Kb free)
events: ring buffer is 256 Kb (12 Kb consumed, 243 Kb free)
crash: ring buffer is 256 Kb (3 Kb consumed, 252 Kb free)
radio: ring buffer is 256 Kb (0 Kb consumed, 256 Kb free)
你看,main 缓冲区才 256KB,稍微一刷就满了。我之前调试一个 Notification 闪烁问题,就是因为这个缓冲区太小,关键日志还没来得及看就被推掉了。
怎么持久化?我的做法是:
adb logcat -v threadtime -b main -b system -b events -f /data/local/tmp/fulllog.txt &
记得在 root 权限下运行,否则 /data 目录你可能没写权限。线上设备我建议用 -b crash 单独保存崩溃日志:
adb logcat -v threadtime -b crash -f /data/anr/tombstone_$(date +%s).log
3.4 实战技巧与避坑
-
日志被吞了怎么办?
我曾经在项目里遇到一个诡异的 StatusBar 崩溃,每次堆栈都少一行。后来发现是因为日志量太大,logd 把低优先级的日志丢弃了。解法:把关键日志级别提到 W 以上,或者用logcat -b events找事件日志辅助定位。 -
pidcat 是好东西
开源工具pidcat.py可以根据进程 PID 过滤日志,比 Tag 更精准。比如只看 SystemUI 自己的日志:
pidcat com.android.systemui
它还会自动按 Tag 高亮,非常直观。 -
dumpsys 日志配合使用
遇到 ANR 或无响应时,记得先抓一份dumpsys activity和dumpsys window,里面经常有 logcat 里看不到的信息。比如输入事件没响应,可以用dumpsys input看 InputDispatcher 队列。 -
定时自动轮转
线上持久化日志时,别忘了加大小限制。可以用logrotate或者自己写个脚本,每 10 分钟切一个文件,避免单文件撑爆磁盘。
adb logcat -b events | grep "touch" 定位过一次输入事件丢包的问题。
好了,这一章的内容基本上覆盖了你平时调试 SystemUI 最核心的日志手法。记住:日志是崩溃分析的侦察兵,而 logcat 是你手里的望远镜。多用、多试、多总结,慢慢你就能从日志中嗅到崩溃的蛛丝马迹。