4. 启动瓶颈分析:用Systrace定位主线程耗时、Binder调用、HAL层阻塞
好,咱们进入实战环节了。前面几章讲了理论,这一章我带你亲手抓一次瓶颈。
你想想看,相机启动慢,问题到底出在哪?是主线程被卡住了?还是某个Binder调用在排队?又或者是HAL层直接睡大觉?
说实话,光靠猜是没用的。我个人的习惯是——先拉一条Systrace,让数据说话。
4.1 为什么是Systrace?
Systrace这工具,说白了就是Android系统的“黑匣子”。它能记录下每一毫秒CPU在干什么、线程在等什么、Binder在传什么。
我在项目中遇到过好几次,开发同学说“我代码没问题啊”,结果Systrace一拉,主线程上一个锁等了300ms,问题一目了然。
嗯,这里要注意:Systrace不是万能的,但它是定位启动瓶颈的第一把刀。
核心思路: 从App进程启动到第一帧预览显示,这中间的每一段耗时,都要在Systrace上找到对应的“坑”。
4.2 抓取一条有效的Systrace
抓trace也有讲究。不是随便抓一条就能用的。我建议你这样操作:
- 关闭后台应用——减少干扰,让结果更干净
- 设置buffer足够大——我一般设2048,防止trace被截断
- 指定要跟踪的进程——只抓相机进程和system_server,别全抓
命令行长这样:
# 抓取相机启动过程的trace
python systrace.py -t 10 -b 2048 \
-a com.android.camera \
-o camera_startup.html \
sched freq idle am wm gfx view binder_driver hal
这里我特别加了 binder_driver 和 hal 两个tag。为什么?因为相机启动的瓶颈,十有八九就藏在这两个地方。
小技巧: 抓trace时,手机最好用USB3.0口,不然数据量大了容易丢帧。我吃过这个亏,折腾了半天才发现是线的问题。
4.3 主线程耗时——第一眼要看的地方
打开生成的HTML文件,第一件事就是找到你的相机进程。然后看主线程(通常是“main”或“CameraApp”)。
主线程上如果有大段的空白,或者颜色特别深的色块,那就是在“干活”。但问题是——它干的是不是该干的活?
我一般会关注这几个关键方法:
| 方法名 | 正常耗时 | 异常信号 |
|---|---|---|
| onCreate | < 50ms | 超过100ms,说明布局或初始化太重 |
| openCamera | < 100ms | 超过200ms,可能是HAL层阻塞 |
| startPreview | < 80ms | 超过150ms,检查Surface创建流程 |
| onResume | < 30ms | 超过50ms,有耗时操作跑到了主线程 |
举个例子。有一次我优化某款手机的相机启动,发现 onCreate 里居然做了数据库查询。嗯,这明显不该出现在主线程上。移到异步线程后,启动快了120ms。
避坑指南: 我曾经遇到一个案例,主线程看起来没阻塞,但启动就是慢。后来发现是 View 的 onMeasure 被重复调了十几次。Systrace上看不到具体布局细节,得配合 Layout Inspector 才能定位。所以别只依赖一个工具。
4.4 Binder调用——跨进程的“隐形杀手”
相机启动过程中,App和CameraService之间要来回调很多次Binder。每一次Binder调用,都是一次跨进程通信。
在Systrace上,Binder调用长这样:
- App线程上出现
binder transaction或binder reply - 对应的,CameraService线程上出现
binder transaction executed - 如果中间有等待,会出现
binder wait
我个人习惯是,先看 binder wait 的时长。如果超过50ms,就要点进去看具体在等什么。
常见的Binder瓶颈有:
- CameraService线程繁忙——多个App同时请求相机,导致排队
- HAL层响应慢——Binder调用到了HAL,但HAL没及时返回
- Binder buffer满——传输的数据量太大,比如传了一个大Bitmap
你看,Binder本身不慢,慢的是它两端的处理逻辑。
4.5 HAL层阻塞——真正的“硬骨头”
到了HAL层,事情就复杂了。因为HAL层跑在独立的进程中,Systrace只能看到它“卡住了”,但不知道卡在哪。
不过,还是有迹可循的。在Systrace上,如果看到 HAL::openCamera 或 HAL::configureStreams 的色块特别长,那基本可以断定是HAL层的问题。
我遇到过最夸张的一次,openCamera 花了800ms。后来追到HAL层代码,发现是驱动初始化时做了一次 sync 操作,把整个文件系统刷了一遍。嗯,这种问题只能找驱动团队一起看。
定位HAL层阻塞的步骤:
- 在Systrace上找到
CameraService进程 - 找到对应的
openCamera或configureStreams调用 - 看调用时长,如果超过200ms,标记为可疑
- 配合
logcat中的HAL层日志,确认具体卡在哪个函数 - 如果还不行,只能加HAL层的自定义trace点
4.6 实战案例:一次完整的瓶颈分析
好,咱们走一遍完整的流程。假设现在有一台手机,相机启动需要1.8秒,目标优化到1.2秒。
第一步:抓trace
按上面说的命令抓一条,打开后看到主线程上有明显的空白段。
第二步:定位主线程问题
放大看,发现 onCreate 里有一段 SharedPreferences 的读取操作,耗时45ms。虽然不大,但加上其他操作,累计到了80ms。
第三步:看Binder调用
继续往下翻,发现 openCamera 的Binder调用花了350ms。点进去看,binder wait 占了200ms。说明CameraService那边在排队。
第四步:追到HAL层
切换到CameraService进程,找到对应的 openCamera 处理。发现HAL层的 camera_device_open 花了280ms。嗯,问题找到了。
第五步:给出优化方案
- 主线程:把
SharedPreferences读取移到异步线程 - Binder:检查是否有其他App占用了CameraService,考虑加优先级
- HAL层:联系驱动团队,优化
camera_device_open中的初始化逻辑
最终,这三项优化加起来,启动时间从1.8秒降到了1.1秒。效果还不错。
4.7 用一张图总结分析流程
下面这张图,是我自己总结的Systrace分析流程。每次拿到一条trace,我就按这个顺序走一遍,基本不会漏掉关键点。
这张图从左到右,从上到下,就是我每次做启动优化的标准流程。你照着走一遍,基本能把90%的瓶颈揪出来。
4.8 本章小结
好,这一章的内容就到这。咱们讲了怎么用Systrace定位三类核心瓶颈:
- 主线程耗时——看onCreate、onResume这些关键方法
- Binder调用——关注binder wait和CameraService线程状态
- HAL层阻塞——配合logcat,追到驱动层面
说实话,Systrace这工具,用好了就是一把利器。但别指望一次就能把所有问题都找出来。我做了这么多年优化,有时候一条trace要看三四遍才能发现隐藏的问题。
下一章,咱们会深入讲怎么用Perfetto做更精细的分析。嗯,到时候再聊。
一句话记住: 启动优化,先拉trace,再看主线程,然后追Binder,最后查HAL。顺序别搞反了。
公众号:蓝海资料掘金营,微信deep3321