21、系统调试与性能优化:logcat、systrace、perfetto、内存泄漏检测
做嵌入式Android开发,说白了就是跟资源较劲。CPU就那么点频率,内存就那么几百兆,屏幕刷新率还卡着上限。系统跑着跑着卡顿了,或者莫名其妙内存爆了,这时候你怎么办?
我刚开始做嵌入式系统时,遇到问题第一反应就是加日志。加了一堆Log.d,跑起来一看,全是垃圾信息。后来才明白——调试不是靠蛮力,是靠工具。今天我就把几个压箱底的调试工具掰开揉碎讲给你听。
核心观点:嵌入式调试的本质是「定位瓶颈」。logcat帮你找逻辑错误,systrace和perfetto帮你找性能瓶颈,内存检测帮你找资源泄漏。缺一不可。
21.1 logcat:最基础的日志工具
logcat是Android日志系统的核心。它把系统日志分成了几个等级:V(Verbose)、D(Debug)、I(Info)、W(Warning)、E(Error)。我个人的习惯是,开发阶段用D,发布前全改成W以上,不然日志量太大。
常用的过滤方式有几种:
- 按标签过滤:
adb logcat -s MyAppTag,只显示你关心的日志 - 按优先级过滤:
adb logcat *:W,只看警告和错误 - 按进程过滤:
adb logcat --pid=1234,只看某个进程的日志
我在项目中遇到过一个问题:某个传感器数据偶尔丢失,但日志里全是系统其他模块的垃圾信息。后来我用adb logcat -b main -s SensorHub只抓传感器相关的日志,问题立刻暴露了——是某个中断处理函数里忘了释放锁。
小技巧:logcat支持多级缓冲。用-b main抓主日志,-b events抓事件日志,-b crash抓崩溃日志。崩溃日志单独抓,不会被其他日志冲掉。
21.2 systrace:系统级性能追踪
systrace是Google早期推出的性能分析工具。它通过ftrace机制,把CPU调度、进程状态、内核事件全部记录下来。说白了,就是给系统拍一张「心电图」。
使用方式很简单:
# 抓取5秒的trace
python systrace.py -t 5 -o mytrace.html
# 只抓特定模块
python systrace.py -t 5 -o mytrace.html gfx input view webview
生成的HTML文件可以在浏览器里打开。你会看到一条时间轴,上面有各个进程的运行状态。绿色表示运行中,蓝色表示可运行,白色表示休眠。如果某个进程长时间处于蓝色状态,说明它在等CPU——嗯,这就是瓶颈。
我记得有一次,系统UI滑动卡顿。用systrace一看,发现SurfaceFlinger进程每隔几百毫秒就出现一个红色标记,点开一看,是GPU渲染超时。后来把某个动画的帧率从60fps降到30fps,问题解决了。
注意:systrace在Android 10之后已经被Perfetto取代。但很多老设备还在用,所以这个工具你还是要会。另外,systrace抓取时间不宜过长,5-10秒就够了,否则文件太大打不开。
21.3 Perfetto:新一代性能追踪工具
Perfetto是systrace的升级版。它支持更细粒度的追踪,比如内存分配、文件I/O、网络请求。而且它的数据格式是protobuf,解析效率更高。
Perfetto有两种使用方式:
- 命令行模式:
adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace -t 10s sched freq gfx - UI模式:在浏览器打开
ui.perfetto.dev,直接拖拽trace文件分析
我个人更喜欢用Perfetto的SQL分析功能。它把trace数据存成了SQLite表,你可以写SQL查询。比如:
-- 查询所有运行时间超过10ms的slice
SELECT name, dur FROM slice WHERE dur > 10000000 ORDER BY dur DESC
这个功能太强了。你想想看,以前用systrace只能肉眼盯着时间轴找异常,现在一条SQL就能把最耗时的操作列出来。我在做低功耗优化时,就用这个查到了某个定时器回调里做了大量字符串拼接,导致CPU频繁唤醒。
对比总结:systrace适合快速看整体情况,Perfetto适合深入分析具体问题。如果你还在用systrace,我建议尽快切换到Perfetto。
21.4 内存泄漏检测
内存泄漏是嵌入式开发的噩梦。系统内存本来就小,泄漏一点就崩。我见过一个项目,跑了两天之后系统自动重启,查了半个月才发现是某个Service里持有了Activity的引用。
常用的检测工具有:
| 工具 | 原理 | 适用场景 |
|---|---|---|
| LeakCanary | 基于WeakReference和ReferenceQueue | 应用层内存泄漏 |
| MAT (Memory Analyzer Tool) | 分析heap dump文件 | 深入分析对象引用链 |
| Android Profiler | 实时监控内存使用 | 快速定位内存波动 |
LeakCanary是我最常用的。它会在检测到泄漏时自动生成一个heap dump,并给出泄漏路径。比如:
// 典型的泄漏路径
com.example.MyActivity
└─ com.example.MySingleton (持有Activity的Context)
└─ android.content.Context
看到这个路径,你就知道是单例持有了Activity的Context。解决办法很简单:把Context换成ApplicationContext。
避坑指南:我曾经在某个项目里发现内存泄漏,但LeakCanary死活检测不出来。后来手动dump了heap,用MAT分析,发现是JNI层分配的内存没有释放。记住,LeakCanary只检测Java堆,不检测Native堆。Native内存泄漏要用malloc debug或AddressSanitizer。
21.5 实战:综合调试流程
说了这么多工具,到底怎么用?我总结了一套流程:
- 先看logcat:有没有明显的错误日志?比如NullPointerException、OutOfMemoryError
- 再用Perfetto:抓一段trace,看CPU、GPU、内存的使用情况。有没有某个进程长时间占用CPU?
- 然后查内存:用Android Profiler看内存曲线。如果持续增长,大概率有泄漏
- 最后用LeakCanary:定位具体的泄漏点和引用链
这套流程我用了好几年,基本能解决90%的性能问题。剩下的10%,嗯,那就得看硬件了——比如某个外设驱动有bug,或者DMA传输有冲突,那就不是软件工具能解决的了。
核心原则:调试不是一次性的,而是迭代的。改一个问题,再抓一次trace,看看有没有引入新问题。尤其是嵌入式系统,牵一发而动全身。