25、AMS 日志与调试:logcat关键tag、dumpsys activity命令、Systrace分析
做Android Framework开发,说白了就是跟各种疑难杂症打交道。Activity启动闪退?应用无响应?页面跳转卡顿?这些问题,十有八九都得靠日志和调试工具来定位。今天我就把压箱底的调试三板斧拿出来聊聊——logcat、dumpsys、Systrace。这三样东西用好了,能省你大半的排查时间。
25.1 logcat 关键Tag:从海量日志里捞针
logcat每天能刷出几万行日志,但真正有用的就那么几个Tag。我个人习惯,先把不相关的过滤掉,只盯着AMS相关的Tag看。
| Tag | 优先级 | 说明 |
|---|---|---|
| ActivityManager | I / W / E | Activity生命周期、启动流程、ANR信息 |
| WindowManager | I / W | 窗口焦点变化、窗口添加/移除 |
| InputDispatcher | I / W / E | 触摸事件分发、ANR触发点 |
| SystemServer | I / W | AMS启动过程、服务注册 |
| PackageManager | I / W | 应用安装、权限检查 |
举个例子,Activity启动失败时,你会在logcat里看到类似这样的信息:
// 启动Activity时AMS的关键日志
I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.example.app/.MainActivity} from uid 1000
I ActivityManager: Start proc 12345:com.example.app/u0a123 for activity {com.example.app/.MainActivity}
W ActivityManager: Activity pause timeout for ActivityRecord{xxx}
E ActivityManager: ANR in com.example.app (com.example.app/.MainActivity)
你看,从START到Start proc,再到pause timeout,最后ANR,每一步都有日志。我曾经遇到过一个诡异的问题:Activity启动后黑屏,但logcat里没有任何错误。后来我加了WindowManager的Tag一看,发现窗口焦点根本没切过去——原来是上一个Activity的onPause里做了耗时操作,把主线程卡住了。
25.2 dumpsys activity:系统状态的X光机
logcat看的是过程,dumpsys看的是状态。你想想看,当Activity启动失败时,你总得知道当前系统里到底有哪些Activity、它们是什么状态吧?dumpsys activity就是干这个的。
我最常用的几个子命令:
- dumpsys activity activities —— 查看所有ActivityRecord,包括栈信息、状态、intent
- dumpsys activity processes —— 查看所有进程信息,包括oom_adj、pid、uid
- dumpsys activity services —— 查看已绑定的Service列表
- dumpsys activity broadcasts —— 查看广播接收器注册情况
输出内容长什么样?我截取一段实际调试时的输出:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #1:
Task id #123
* Hist #0: ActivityRecord{xxx u0 com.example.app/.MainActivity t123}
Intent: {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]}
ProcessRecord: xxx (pid 12345)
State: RESUMED
WindowState: ADDED_AT (visible)
这里的信息量很大。State是RESUMED说明Activity在前台,WindowState是ADDED_AT说明窗口已经添加。如果State是PAUSING或者STOPPING,那说明切换流程卡住了。我遇到过好几次,Activity跳转后黑屏,dumpsys一看State是PAUSING,再配合logcat的pause timeout日志,问题就锁定了——是上一个Activity的onPause里做了网络请求。
dumpsys activity activities | grep -E "State|pid|Stack"。别傻乎乎地从头看到尾,那会累死。
25.3 Systrace分析:把时间线画出来
logcat和dumpsys能告诉你发生了什么,但很难告诉你「为什么这么慢」。Systrace就不一样了,它把系统里每个线程、每个方法调用都按时间线画出来。说白了,就是给系统做了一次心电图。
我一般这样用Systrace:
- 先确定要追踪的场景,比如「点击图标到Activity显示」
- 用
python systrace.py -t 10 -o trace.html am wm gfx input view抓取10秒 - 在生成的HTML里找到对应的区间,看每个阶段花了多少时间
一个典型的Activity启动流程,在Systrace里会看到这些阶段:
- handleLaunchActivity —— AMS开始处理启动请求
- bindApplication —— 绑定应用进程
- activityStart —— ActivityThread开始启动
- onCreate / onStart / onResume —— 生命周期回调
- performTraversals —— 第一次布局和绘制
我记得有一次,用户反馈某个页面打开特别慢。用Systrace一看,onCreate里有个数据库查询花了800ms,而performTraversals只用了16ms。问题就很清楚了——不是渲染慢,是数据准备慢。后来把查询放到异步线程,启动时间从1.2秒降到了400ms。
核心思路:Systrace的关键不是看「有没有报错」,而是看「时间花在哪了」。我建议重点关注两个指标:一是主线程上有没有超过16ms的连续任务(掉帧),二是AMS和App进程之间的Binder调用耗时。
25.4 三件套配合使用:一个实战案例
光说理论没意思,我拿一个真实案例串一下。有一次线上反馈,某款应用在低端机上启动后白屏5秒才显示内容。
第一步,我先用logcat过滤ActivityManager:
I ActivityManager: Displayed com.example.app/.MainActivity: +3s200ms
嗯?Displayed时间只有3.2秒,但用户说白屏5秒。说明问题不在AMS的启动流程里,而在后面的渲染阶段。
第二步,用dumpsys activity activities看看窗口状态:
State: RESUMED
WindowState: ADDED_AT (visible)
窗口已经可见了,但用户看到的是白屏。那问题大概率出在SurfaceFlinger或者GPU合成上。
第三步,抓Systrace,重点看performTraversals和doFrame。结果发现:
- performTraversals只用了12ms,正常
- 但doFrame里有一个
Choreographer#doFrame的间隔达到了4.8秒 - 点进去一看,是主线程在等一个
CountDownLatch,而那个锁在子线程里被一个网络请求卡住了
问题就水落石出了——子线程的网络请求超时,导致主线程的锁等待,虽然Activity已经显示,但UI线程被阻塞,无法处理后续的绘制指令。用户看到的就是白屏。
25.5 知识体系总览
下面这张图是我自己总结的AMS调试知识体系,你可以把它当成一个快速索引。遇到问题时,先看属于哪个层面,再选对应的工具。
嗯,这套调试方法我用了好多年,从Android 4.4到现在的Android 14,核心思路一直没变。你只要把这三样工具练熟了,大部分AMS相关的问题都能在半小时内定位到根因。剩下的,就是修代码的事了。
公众号:蓝海资料掘金营,微信deep3321