10、启动速度优化:Camera冷启动流程分析、Service预热、Provider预加载、SurfaceFlinger优化、缩短第一帧时间

相机启动慢,是用户吐槽最多的问题之一。我见过不少项目,冷启动时间动辄两三秒,用户打开相机还以为死机了。说实话,这个优化做不好,其他功能再强也白搭。

今天咱们就聊聊,怎么把相机启动的第一帧时间,从一两秒压到几百毫秒。我会结合我踩过的坑,把冷启动的每个环节拆开来讲。

10.1 冷启动流程全景分析

先看一张图,了解相机冷启动到底走了哪些路。

Camera 冷启动流程 App 进程启动 CameraService 绑定 Provider 加载 HAL 打开 Stream 配置 Surface 创建 第一帧 Request 预览显示 0ms ~200ms ~500ms ~1000ms+ 优化切入点 ① Service 预热:提前绑定 CameraService ② Provider 预加载:ContentProvider 提前初始化 ③ SurfaceFlinger 优化:减少 Buffer 排队延迟 ④ 缩短第一帧:精简 Request 流程,跳过非必要步骤

从图上能看出来,冷启动主要分两大块:服务初始化第一帧渲染。服务初始化包括 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 秒,用户投诉率很高。我们做了以下优化:

  1. Service 预热:在系统启动时提前加载 CameraService,节省 200ms。
  2. Provider 预加载:将数据库迁移改为异步,节省 150ms。
  3. SurfaceFlinger 优化:减少 Buffer 排队深度,节省 30ms。
  4. 精简 Request 流程:跳过预热后的重复检查,节省 100ms。
  5. 复用 Stream 配置:保留热备份,节省 100ms。

最终冷启动时间从 1.8 秒降到了 1.1 秒。虽然还没到 800ms 的理想值,但用户满意度已经大幅提升。后续我们还在继续优化 HAL 层的打开时间,目标是压到 800ms 以内。

总结一句话:相机冷启动优化,核心就是「能提前做的绝不拖到后面做,能跳过的绝不多做一次」。Service 预热、Provider 预加载、SurfaceFlinger 优化、精简 Request 流程,这四个方向做好了,第一帧时间就能大幅缩短。

公众号:蓝海资料掘金营,微信deep3321