二、启动流程总体概览:从初始化到第一帧

先问一个问题:当你调用 ProcessCameraProvider.getInstance() 拿到 provider,再 bindToLifecycle() 之后,到底经历了哪些步骤才能看到画面?

我早年第一次接触 CameraX 时,以为就是“绑定、预览、完事”。后来线上用户反映冷启动第一帧要等 2 秒多,我才意识到——每一步都可能藏着坑。今天咱们把 CameraX 从初始化到第一帧的完整链路走一遍,给后续的逐段分析搭好框架。

2.1 核心阶段划分

整个启动流程可以拆成 5 个阶段,我个人习惯用“收-绑-配-开-显”来记:

  1. 初始化阶段 —— Provider 创建 + Camera 列表枚举
  2. 生命周期绑定阶段 —— LifecycleOwner 关联 + 用例注册
  3. Session 配置阶段 —— CameraConfig 生成 + Surface 预分配
  4. CaptureSession 开启阶段 —— 内核 CameraDevice 开启 + 创建 Session
  5. 第一帧显示阶段 —— 首次 Preview 帧回调 → Surface 呈现

每个阶段之间还有状态切换和线程调度开销,这些往往被忽略。我之前在一个低端机上遇到第一帧迟迟不显示,查了半天发现是线程池排队太久——说白了就是 CPU 核不够,任务被卡住了。

2.2 阶段耗时权重

根据我多次实测(中端骁龙 6 系,1080p Preview),各阶段典型耗时占比大致如下:

阶段 典型耗时 (ms) 占比 主要瓶颈点
初始化80–150~15%CameraService 通信、枚举 CameraID
生命周期绑定30–60~6%UseCase 校验、Config 合并
Session 配置100–200~20%Surface 分配、CameraConfig 序列化
CaptureSession 开启200–400~45%Camera HAL 打开、Stream 配置
第一帧显示60–100~14%首次帧传递、SurfaceTexture 渲染

关键结论:一半以上的时间花在 CaptureSession 开启上。所以优化启动速度,首先要盯着 CameraDevice 创建和 Stream 配置这块。

2.3 流程图:五站接力

下面这张图把五个阶段串了一遍。每个阶段内部还有子步骤,这张图先给个宏观印象,后续章节我再拆开讲。

CameraX 启动流程全链路 ① 初始化阶段 Provider.getInstance() ② 生命周期绑定 bindToLifecycle() ③ Session 配置 Surface 预分配 ④ Session 开启 CameraDevice + Session ⑤ 第一帧显示 Preview 帧 → Surface 呈现 ⚠️ 关键路径说明: • 初始化阶段阻塞主线程?CameraX 内部会移到后台线程,但 getInstance() 等待Future仍会卡一下。 • 生命周期绑定阶段需要确保 LifecycleOwner 已到 CREATED 状态,否则会延迟。 • Session 开启阶段是最重的:HAL 层打开 CameraDevice、配置 Stream 都走binder。 • 第一帧的到达依赖硬件级 pipeline,Preview 帧率可能被其他 UseCase 的帧约束拖慢。

2.4 各阶段关键细节

2.4.1 初始化阶段

这一步主要做三件事:bind Service ↔ 获取 Camera 列表 ↔ 初始化 CameraX 内部状态机。你在主线程调 ProcessCameraProvider.getInstance() 时,它会返回一个 ListenableFuture,内部其实在 binder 线程池里跑。我曾遇到过某个厂商 ROM 在这个阶段因为 getCameraCharacteristics 卡了 300ms——嗯,这就是典型的底层适配问题。

个人小建议:如果有条件,提前在 Application 里做一次预初始化,把 Future 启动起来。不过要注意,不要过早获取 camera 列表以免影响其他 App。

2.4.2 生命周期绑定阶段

当你调用 cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview),CameraX 会:

  • 检查 LifecycleOwner 当前状态(至少是 CREATED)
  • 根据 CameraSelector 选择合适的相机
  • 把所有 UseCase(Preview、ImageAnalysis 等)的配置合并成一个 SessionConfig

这里有个坑:如果你在 Activity 的 onCreate 里直接绑,而 LifecycleOwner 还没到 CREATED,绑定操作会被推迟到 ON_START 时执行。说白了,你感觉代码写了,实际还没执行。我曾经为了贪图方便在 onCreate 里就绑,结果第一帧延迟了整整一个帧周期。

2.4.3 Session 配置阶段

这个阶段在 CameraX Core 内部处理,对开发者几乎是透明的。它做什么?把 Preview 和 ImageAnalysis 需要的 Surface 尺寸、格式整理好,然后通过 Camera2 的 createCaptureSession 传给 HAL。注意:Surface 分配如果是 new SurfaceTexture() 方式,默认大小为 0,得等 Surface 被消费者 setDefaultBufferSize 才算真正可用——这也是耗时点之一。

2.4.4 CaptureSession 开启阶段

嗯,这个阶段是重头戏。它包含:

  • 进程间通信打开 Camera HAL(耗时大头)
  • 配置 V4L2 或 ISP 的 stream
  • 创建 CameraCaptureSession 并设置 StateCallback
  • 发送第一次重复请求 (RepeatingRequest)

你想想看,每次打开相机本质上就是跟硬件驱动走一趟 IPC,对于双摄甚至三摄手机,还要协调多个 camera ID。我在做某款弹出式摄像头机型适配时,发现这一步额外多了 100ms——因为 HAL 层要等机械结构弹出并回传状态。

曾经踩过的坑:有人试图通过减小 Preview 分辨率来加速 Session 创建,但代价是按比例缩放导致画面模糊。更聪明的做法是检查 targetFpsRange 是否与 Session 配置匹配——不匹配时 HAL 可能会重新协商参数,白白浪费时间。

2.4.5 第一帧显示阶段

Session 开启后 CameraX 会自动发送第一个 repeating request,第一帧从 sensor 捕获 → ISP 处理 → 送到 PreviewSurface → 渲染到 View。这里一般 60~100ms,但如果你的 SurfaceTexture 在预览控件还没 attach 到 window 上,第一帧可能会被丢弃。我记得有一次就是在 Fragment 的 onViewCreated 里绑早了,SurfaceView 还没完成创建,结果等了好几个帧周期才看到画面。

2.5 总结

到现在你应该对 CameraX 从初始化到第一帧的整体轮廓有数了:

  • 五段接力:初始化 → 绑定 → 配置 → 开启 → 第一帧
  • Session 开启占了大头,先优化这块
  • 各阶段内部都有线程调度、IPC、HAL 通信的隐性耗时