10、启动速度优化:Camera冷启动流程分析、Service预热、Provider预加载、SurfaceFlinger优化、缩短第一帧时间
相机启动慢,是用户吐槽最多的问题之一。我见过不少项目,冷启动时间动辄两三秒,用户打开相机还以为死机了。说实话,这个优化做不好,其他功能再强也白搭。
今天咱们就聊聊,怎么把相机启动的第一帧时间,从一两秒压到几百毫秒。我会结合我踩过的坑,把冷启动的每个环节拆开来讲。
10.1 冷启动流程全景分析
先看一张图,了解相机冷启动到底走了哪些路。
从图上能看出来,冷启动主要分两大块:服务初始化和第一帧渲染。服务初始化包括 App 进程启动、CameraService 绑定、Provider 加载、HAL 打开。第一帧渲染则包括 Stream 配置、Surface 创建、Request 提交、预览显示。
我个人习惯把优化目标定在 800ms 以内。超过这个数,用户就能明显感觉到「卡」了。
10.2 Service 预热:让 CameraService 提前就位
CameraService 是相机框架的核心。它负责管理 HAL、处理请求、分发事件。冷启动时,App 需要先绑定 CameraService,这个过程涉及跨进程通信,耗时不少。
我在项目中遇到过一个问题:每次冷启动,CameraService 都要从头加载 HAL 库,光这一步就占了 200ms。后来我们做了 Service 预热,效果立竿见影。
10.2.1 什么是 Service 预热?
说白了,就是在用户真正打开相机之前,提前把 CameraService 启动起来。比如在系统启动时、或者 App 预加载阶段,先 bindService 一次。
核心思路:让 CameraService 常驻内存,避免冷启动时的首次加载开销。
10.2.2 预热实现方式
有两种常见做法:
- 系统级预热:在 SystemServer 启动时,提前创建 CameraService 实例。适合系统相机。
- 应用级预热:在 App 启动时,通过 bindService 提前绑定。适合第三方相机 App。
代码示例(应用级预热):
// 在 Application.onCreate 中提前绑定
public class CameraApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// 提前绑定 CameraService
Intent intent = new Intent("android.media.action.STILL_IMAGE_CAMERA");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// Service 已就绪,后续打开相机更快
Log.d("CameraApp", "CameraService 预热完成");
}
@Override
public void onServiceDisconnected(ComponentName name) {}
}, Context.BIND_AUTO_CREATE);
}
}
小提示:预热时机要选好。太早可能浪费资源,太晚又没效果。我一般选在用户进入相机相关页面之前 1-2 秒触发。
10.3 Provider 预加载:减少 ContentProvider 初始化开销
很多相机 App 会用到 ContentProvider 来管理媒体数据、权限检查等。Provider 的初始化是在 App 进程启动时完成的,如果 Provider 太多或者初始化逻辑太重,会拖慢启动速度。
我曾经见过一个项目,Provider 的 onCreate 里做了数据库迁移、文件扫描、网络请求……结果冷启动直接多了 300ms。嗯,这里要注意,Provider 不是给你做重活的地方。
10.3.1 预加载策略
- 延迟初始化:非关键 Provider 放到后台线程初始化,不要阻塞主线程。
- 预创建 Provider:在 Application 启动时,提前调用 ContentResolver 触发 Provider 创建。
- 精简 Provider 逻辑:只做必要的初始化,其他操作异步执行。
代码示例(延迟初始化):
public class CameraProvider extends ContentProvider {
@Override
public boolean onCreate() {
// 只做最轻量的初始化
Log.d("CameraProvider", "轻量初始化完成");
// 重活放到后台
new Thread(() -> {
// 数据库迁移、文件扫描等
doHeavyInit();
}).start();
return true;
}
}
注意:ContentProvider 的 onCreate 是在主线程执行的。如果你在里面做了耗时操作,直接拖慢 Application 启动。我曾经踩过这个坑,后来全部改成了异步。
10.4 SurfaceFlinger 优化:减少 Buffer 排队延迟
SurfaceFlinger 负责合成所有窗口的 Buffer 并显示到屏幕。相机预览的每一帧,都要经过 SurfaceFlinger 的合成。如果 SurfaceFlinger 负载高,或者 Buffer 排队太长,第一帧就会延迟。
我优化过一个项目,SurfaceFlinger 的 Buffer 排队深度是 3,导致第一帧要多等 2 个 vsync 周期。改成 1 之后,第一帧时间直接缩短了 30ms。
10.4.1 优化点
- 减少 Buffer 排队深度:将 BufferQueue 的 maxDequeuedBufferCount 设为 1,减少等待。
- 提高 SurfaceFlinger 优先级:在关键路径上提高 SurfaceFlinger 的线程优先级。
- 减少合成层数:尽量使用 Hardware Composer,减少 GPU 合成开销。
代码示例(设置 Buffer 排队深度):
// 在 Camera HAL 或 CameraService 中设置
Surface surface = ...;
SurfaceControl surfaceControl = ...;
// 设置 maxDequeuedBufferCount 为 1
surfaceControl.setMaxDequeuedBufferCount(1);
关键指标:SurfaceFlinger 的合成延迟应该控制在 1 个 vsync 周期以内(16.6ms @60Hz)。如果超过,就要排查是不是 Buffer 排队或者 GPU 负载问题。
10.5 缩短第一帧时间:从 Request 到 Preview
第一帧时间,指的是从用户点击「打开相机」到屏幕上出现预览画面的时间。这是用户最直观的感受。我习惯把这个时间拆成三段:
| 阶段 | 耗时占比 | 优化方向 |
|---|---|---|
| Service 绑定 + Provider 加载 | ~40% | 预热、延迟初始化 |
| HAL 打开 + Stream 配置 | ~35% | 精简配置、复用 Stream |
| 第一帧 Request + 显示 | ~25% | 减少 Buffer 排队、优化 SurfaceFlinger |
10.5.1 精简 Request 流程
默认的 Camera 打开流程,会做很多检查:权限检查、配置验证、参数校准……有些检查在预热阶段已经做过了,可以跳过。
代码示例(跳过非必要检查):
// 在 CameraService 中,如果已经预热过,跳过部分检查
if (isPreWarmed) {
// 跳过权限检查(已在预热时完成)
// 跳过参数校准(使用预热时的缓存参数)
openCameraInternal(cameraId, callback, /* skipChecks */ true);
} else {
openCameraInternal(cameraId, callback, /* skipChecks */ false);
}
10.5.2 复用 Stream 配置
每次打开相机都要重新配置 Stream,这涉及 HAL 层的资源分配。如果能够复用上一次的 Stream 配置,可以省下不少时间。
我做过一个优化:在相机退出时,不销毁 Stream,而是保留一个「热备份」。下次打开时直接复用,省掉了 100ms 左右的配置时间。
避坑指南:复用 Stream 要注意资源释放。如果长时间不用,还是要销毁,否则会占用内存。我一般设置一个 30 秒的超时,超时后自动释放。
10.6 实战案例:某项目冷启动优化记录
最后分享一个我实际做过的优化案例。某款手机,冷启动时间 1.8 秒,用户投诉率很高。我们做了以下优化:
- Service 预热:在系统启动时提前加载 CameraService,节省 200ms。
- Provider 预加载:将数据库迁移改为异步,节省 150ms。
- SurfaceFlinger 优化:减少 Buffer 排队深度,节省 30ms。
- 精简 Request 流程:跳过预热后的重复检查,节省 100ms。
- 复用 Stream 配置:保留热备份,节省 100ms。
最终冷启动时间从 1.8 秒降到了 1.1 秒。虽然还没到 800ms 的理想值,但用户满意度已经大幅提升。后续我们还在继续优化 HAL 层的打开时间,目标是压到 800ms 以内。
总结一句话:相机冷启动优化,核心就是「能提前做的绝不拖到后面做,能跳过的绝不多做一次」。Service 预热、Provider 预加载、SurfaceFlinger 优化、精简 Request 流程,这四个方向做好了,第一帧时间就能大幅缩短。
公众号:蓝海资料掘金营,微信deep3321