9、日志高级技巧:logcat 按标签过滤、logcat 按 PID 过滤、清空日志缓冲区、实战:分析 ANR 日志

日志这东西,说白了就是系统的「黑匣子」。我刚入行那会儿,觉得看日志就是瞎猫碰死耗子,满屏滚过去,眼睛都看花了。后来才明白,不是日志没用,是你不会用。今天咱们就把 logcat 的高级玩法掰开揉碎讲清楚。

9.1 为什么需要高级过滤?

你想想看,一个正在运行的 Android 系统,每秒能产生几百条日志。系统服务、应用进程、内核驱动……全混在一起。你要是直接 adb logcat,那画面简直不忍直视。

我记得有一次线上出了个 bug,用户反馈 App 闪退。我连上设备,日志刷得跟瀑布似的,根本找不到关键信息。后来用了标签过滤,十秒钟就定位到了问题。嗯,从那以后,我再也不裸看日志了。

核心原则: 先过滤,再分析。别让噪音干扰你的判断。

9.2 按标签过滤(Tag Filter)

每个日志条目都有个标签(Tag),通常代表模块名或类名。比如 ActivityManagerSystemServerMyApp 等等。

基本语法很简单:

adb logcat -s TagName

举个例子,我只想看 ActivityManager 的日志:

adb logcat -s ActivityManager

这会把其他所有标签都屏蔽掉,只显示 ActivityManager 的内容。干净利落。

如果你想看多个标签,用逗号分隔:

adb logcat -s ActivityManager,WindowManager
我的习惯: 调试自己写的模块时,我会在代码里用统一的 Tag 前缀,比如 "MyApp_Network"、"MyApp_DB"。这样过滤起来特别方便。

还有一种更灵活的写法——用正则表达式过滤:

adb logcat -s "MyApp.*"

这会匹配所有以 "MyApp" 开头的标签。我在项目中遇到过,团队里不同人写的 Tag 风格不统一,有的用下划线,有的用驼峰。用正则一把梭,省心多了。

9.3 按 PID 过滤(Process ID Filter)

有时候,同一个标签可能被多个进程使用。比如 "SystemServer" 这个标签,系统服务里一堆线程都在用。这时候按标签过滤就不够精确了。

你需要的是按进程 ID 过滤。

先找到目标进程的 PID:

adb shell ps | grep com.example.myapp

假设返回的 PID 是 12345,那么:

adb logcat --pid=12345

这就只显示该进程的日志了。其他进程的日志一概不显示。

注意: PID 是动态的,每次重启应用都会变。所以别写死在脚本里,最好动态获取。

我常用的组合拳是这样的:

# 先获取 PID
PID=$(adb shell pidof com.example.myapp)
# 再过滤日志
adb logcat --pid=$PID -v threadtime

为什么要加 -v threadtime?因为这样能看到线程 ID 和时间戳,方便分析并发问题。

9.4 清空日志缓冲区

日志缓冲区是有大小限制的。默认情况下,主缓冲区是 256KB,事件缓冲区是 256KB,崩溃缓冲区是 128KB。满了之后,新的日志会覆盖旧的。

所以,在开始一轮新的调试之前,我建议先清空缓冲区:

adb logcat -c

这个命令会清空所有日志缓冲区。之后再执行 adb logcat,你看到的就是从这一刻开始的新日志。

为什么要这么做?

  • 避免旧日志干扰视线
  • 方便复现问题后直接抓取关键日志
  • 减少缓冲区溢出导致日志丢失的风险
一个小技巧: 清空缓冲区后,立即执行复现步骤,然后马上抓日志。这样日志里只有你关心的内容,分析起来事半功倍。

如果你只想清空某个特定的缓冲区,可以指定 -b 参数:

adb logcat -b main -c    # 只清空主缓冲区
adb logcat -b events -c  # 只清空事件缓冲区

9.5 实战:分析 ANR 日志

ANR(Application Not Responding)是 Android 开发者最头疼的问题之一。说白了,就是你的应用在主线阻塞了太久,系统看不下去了,直接弹个对话框告诉用户「这个应用无响应」。

ANR 的日志分析,核心就三步:

  1. 找到 ANR 发生的进程
  2. 查看主线程的堆栈
  3. 分析阻塞原因

9.5.1 抓取 ANR 日志

当 ANR 发生时,系统会把相关信息写入 /data/anr/traces.txt。你可以用 adb 把它拉下来:

adb pull /data/anr/traces.txt

但更直接的方式是,在 logcat 里实时观察 ANR 事件:

adb logcat -b events | grep "am_anr"

这会过滤出事件缓冲区中所有与 ANR 相关的日志。输出大概长这样:

I am_anr  : [0,12345,com.example.myapp,1234567890,Input dispatching timed out]

其中 12345 就是发生 ANR 的进程 PID。

9.5.2 分析主线程堆栈

拿到 PID 之后,用 --pid 过滤出该进程的所有日志:

adb logcat --pid=12345 -v threadtime

重点关注主线程(通常线程 ID 为 1)的日志。你会看到类似这样的堆栈信息:

2025-01-15 14:30:45.123 12345 12345 I System.out: 
"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x...
  | sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x...
  | state=S schedstat=( 0 0 0 ) utm=0 stm=0 core=0 HZ=100
  | stack=0x7f8c400000-0x7f8c402000 stackSize=8192KB
  | held by tid=5 (AsyncTask #1)
  at com.example.myapp.MainActivity.onClick(MainActivity.java:45)
  - waiting to lock <0x...> (a java.lang.Object)
  at android.view.View.performClick(View.java:7444)
  ...

看到 tid=1 Blocked 了吗?这就是主线程被阻塞了。后面跟着的 waiting to lock 说明它在等一把锁,而锁被 tid=5 的 AsyncTask 持有着。

关键信息:
  • tid=1:主线程
  • Blocked:线程状态为阻塞
  • waiting to lock:在等待锁释放
  • held by tid=5:锁被线程 5 持有

9.5.3 定位根因

找到锁的持有者之后,继续看线程 5 的堆栈:

2025-01-15 14:30:45.124 12345 12349 I System.out: 
"AsyncTask #1" prio=5 tid=5 Sleeping
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x...
  | sysTid=12349 nice=0 cgrp=default sched=0/0 handle=0x...
  | state=S schedstat=( 0 0 0 ) utm=0 stm=0 core=0 HZ=100
  | stack=0x7f8c300000-0x7f8c302000 stackSize=8192KB
  | held by tid=1 (main)
  at java.lang.Thread.sleep(Native Method)
  at com.example.myapp.DataLoader.loadData(DataLoader.java:30)
  ...

线程 5 在 sleep,而且它持有了主线程需要的锁。这就形成了死锁——主线程等线程 5 释放锁,线程 5 在 sleep 不干活。

我曾经踩过的坑: 有一次 ANR 查了半天,发现是后台线程里做了网络请求,还加了同步锁。主线程刚好也要用同一把锁,结果后台线程网络超时,主线程就卡死了。后来我强制规定:主线程绝对不等待后台线程的锁,用回调或者 Handler 来解耦。

9.6 知识体系总览

下面这张图总结了本章的核心知识点,方便你快速回顾:

日志高级技巧知识体系 logcat 高级技巧 按标签过滤 adb logcat -s TagName 支持正则匹配 多标签逗号分隔 按 PID 过滤 adb logcat --pid=12345 结合 pidof 动态获取 配合 -v threadtime 清空日志缓冲区 adb logcat -c 支持指定缓冲区 -b main -c ANR 日志分析 抓取 traces.txt 查看主线程堆栈 定位锁竞争/死锁

9.7 总结

日志分析是 Android 调试的基本功。掌握了按标签过滤、按 PID 过滤、清空缓冲区这三个技巧,你就能在茫茫日志海中快速定位目标。而 ANR 分析,说白了就是找到主线程被谁堵住了,然后解决它。

我个人习惯把常用命令写成脚本,比如 logcat-myapp.sh,一键过滤、清空、输出到文件。调试效率能提升一大截。

记住一句话:日志不会骗人,但前提是你会看。


公众号:蓝海资料掘金营,微信deep3321