性能监控基础:常见性能指标(帧率、延迟、内存、CPU)
做 CameraX 启动优化,其实就是搞定四个数字:帧率、延迟、内存、CPU。我个人习惯把这四个指标称为「性能四件套」。少一个,你就算优化了也不知道优化到哪去了。我曾经吃过大亏——只盯着帧率,结果内存蹭蹭涨,最后被系统直接杀了进程。
嗯,那咱们就一个一个捋清楚。每个指标我会告诉你它是什么、怎么测、以及我在项目中踩过的坑。
帧率:画面流畅度的直接体现
帧率说白了就是每秒钟画面刷新的次数。CameraX 启动时,如果帧率低于 20fps,用户体验就崩了。我习惯用 Choreographer.FrameCallback 来监控,简单实用。
// 个人常用的帧率监控工具
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
long lastFrameTimeNs = 0;
@Override
public void doFrame(long frameTimeNanos) {
if (lastFrameTimeNs != 0) {
float fps = 1_000_000_000f / (frameTimeNanos - lastFrameTimeNs);
Log.d("FpsMonitor", "当前帧率: " + fps);
}
lastFrameTimeNs = frameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
}
});
为什么 CameraX 启动时特别容易出现掉帧?因为启动瞬间要加载预览流、开启相机设备、分配 Surface 等等。我记得有一次项目里,启动帧率从 30fps 掉到 12fps,查了半天发现是 CameraXConfig 设置里有个 setCameraExecutor 没处理好线程优先级。
Trace.beginSection("startup") 结合 Systrace 看帧率掉在哪个阶段,比单纯 Log 精准很多。
延迟:用户感知最大的痛点
延迟包括启动延迟、预览延迟和拍照延迟。对于 CameraX 启动来说,我关注的是「从 ProcessCameraProvider.getInstance 到 PreviewView 显示第一帧」的总耗时。你想想看,用户打开相机,等了 2 秒还是一片黑,他肯定觉得 App 卡死了。
统计延迟我喜欢用 reportFullyDrawn() 配合 ActivityLifecycleCallbacks。具体做法是在 onCameraReady 回调里打点。
// 启动延迟上报示例
fun reportStartupLatency() {
val startTime = System.currentTimeMillis()
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val latency = System.currentTimeMillis() - startTime
Log.i("LatencyMonitor", "CameraX 启动延迟: $latency ms")
}, ContextCompat.getMainExecutor(context))
}
我曾经在一个低端机上测到 1800ms 的延迟,原因竟是 ImageCapture 的 captureMode 配置为 CAPTURE_MODE_MAXIMIZE_QUALITY,导致 CameraX 内部做了大量图像处理。改成 CAPTURE_MODE_MINIMIZE_LATENCY 后直接降到 800ms。
内存:别让 CameraX 变成内存杀手
CameraX 启动时内存会快速上升,主要是 Surface 缓冲区和图像流占用的。我们一般盯着两个数据:Java Heap 和 Native Heap。Android Studio 的 Memory Profiler 可以看,但上线后我推荐用 Debug.MemoryInfo 打点。
| 内存类型 | 监控方式 | 常见问题 |
|---|---|---|
| Java Heap | Runtime.getRuntime().totalMemory() | Bitmap 没及时回收 |
| Native Heap | Debug.getNativeHeapSize() | Surface 缓冲区泄漏 |
| Graphic 内存 | ActivityManager.getProcessMemoryInfo() | 预览帧堆积 |
我遇到过最诡异的 Case:CameraX 退到后台后,内存一直居高不下。查了两天发现是 Preview.SurfaceProvider 没有在 onStop() 里清空。嗯,这个坑其实文档里写了,但没写清楚——如果你用了自定义 SurfaceProvider,一定要在生命周期结束调用 surface.getHolder().removeCallback(this)。
CPU 使用率:负载平衡的艺术
CPU 占用高不一定是坏事,但 CameraX 启动时如果 CPU 长时间跑满,说明有瓶颈。我建议同时看两个维度:
- 总 CPU 使用率:用
/proc/stat读取整体负载 - 进程 CPU 使用率:用
/proc/[pid]/stat算自己的占比
// 简易的进程 CPU 监控(生产环境建议用 Profilo 或 atrace)
fun getProcessCpuUsage(): Float {
val pid = Process.myPid()
val stat = File("/proc/$pid/stat").readText().split(" ")
// utime + stime 是 CPU 时间片
val utime = stat[13].toLong()
val stime = stat[14].toLong()
val totalTime = utime + stime
// 需要与系统总时间做比例计算,这里仅示例
return totalTime / 100f // 简化示意
}
为什么 CameraX 启动会让 CPU 飙升?因为 CameraX 内部使用了多个线程池(CameraExecutor、IoExecutor、MainExecutor),如果它们竞争厉害,CPU 就炸了。我建议把 setCameraExecutor 绑到 AsyncTask.THREAD_POOL_EXECUTOR 或者自定义固定大小线程池上,避免无限扩张。
指标间的关系矩阵
把它们的关系列出来,你就能一眼看懂怎么排查了:
- 帧率低 + 延迟高 → 一般是主线程被阻塞了,检查
SurfaceView的onFrameAvailable - 内存高 + 帧率低 → 可能 GC 频繁,检查
ImageProxy是否及时关闭 - CPU 满载 + 延迟正常 → 某个后台任务在抢线程,检查
ImageAnalysis的分辨率设置 - 四个指标都正常,但用户觉得卡 → 嗯,这可能是动画帧率问题,不是 CameraX 本身的问题
我在一次线上巡检中发现 2% 的用户启动延迟超过 3s,但内存和 CPU 都正常。后来定位到是他们手机温度过高触发了系统的 thermal throttling —— 这种场景下,我们的监控指标要加上设备温度才好分析。
PerformanceMonitor 的单例,每 200ms 采样一次这四个指标,并打一个时间戳。上线后用 Timber.i 输出到日志文件,配合 Crash 分析一起看。这样能还原问题现场,比事后蒙着改代码强多了。