二、启动流程总体概览:从初始化到第一帧
先问一个问题:当你调用 ProcessCameraProvider.getInstance() 拿到 provider,再 bindToLifecycle() 之后,到底经历了哪些步骤才能看到画面?
我早年第一次接触 CameraX 时,以为就是“绑定、预览、完事”。后来线上用户反映冷启动第一帧要等 2 秒多,我才意识到——每一步都可能藏着坑。今天咱们把 CameraX 从初始化到第一帧的完整链路走一遍,给后续的逐段分析搭好框架。
2.1 核心阶段划分
整个启动流程可以拆成 5 个阶段,我个人习惯用“收-绑-配-开-显”来记:
- 初始化阶段 —— Provider 创建 + Camera 列表枚举
- 生命周期绑定阶段 —— LifecycleOwner 关联 + 用例注册
- Session 配置阶段 —— CameraConfig 生成 + Surface 预分配
- CaptureSession 开启阶段 —— 内核 CameraDevice 开启 + 创建 Session
- 第一帧显示阶段 —— 首次 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 流程图:五站接力
下面这张图把五个阶段串了一遍。每个阶段内部还有子步骤,这张图先给个宏观印象,后续章节我再拆开讲。
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 通信的隐性耗时