一、CameraX 简介与架构概述
做 Android 相机开发好几年了,我踩过不少坑。最头疼的就是设备碎片化——同样的代码,在小米上跑得飞快,到华为上就黑屏,再到三星又给你来个「相机服务异常」。后来 Google 推出了 CameraX,说实话,一开始我是拒绝的,总觉得「又一个封装层,性能肯定打折扣」。
但当我真在项目里用上它,嗯,态度变了。不仅是启动流程简化了,连 lifecycle 绑定都自动搞定。今天咱们就来聊聊 CameraX 到底是什么,它背后的架构长什么样。
1.1 什么是 CameraX?
CameraX 是 Android Jetpack 的官方相机库。它直接基于 Camera2 API 构建,但把那些繁琐的 Surface 创建、会话管理、状态机流转都封装掉了。
| 特性 | 说明 |
|---|---|
| 目标 | 降低相机开发复杂度,消除设备兼容性问题 |
| 底层依赖 | Camera2 API(兼容到 API 21) |
| 核心优势 | 自动绑定 Lifecycle、多 UseCase 同时运行、内置兼容层 |
| 版本演进 | 1.0(基础)、1.1(扩展)、1.2(ML Kit 集成) |
说白了,你写 Camera2 原本要写四五百行的配置代码,用 CameraX 可能二三十行就够了。我记得第一次迁移时还有点不信,结果跑起来发现预览、拍照、分析同时开,而且还没崩——那一刻,我是真的服了。
1.2 架构分层
CameraX 并不是一个黑盒子。它内部有清晰的分层,我把它梳理成下面这张图:
从上往下看,App 层只需要调用 ProcessCameraProvider.getInstance() 绑定一个 Lifecycle,然后创建 UseCase 并绑定到同一个生命周期。下层的事情——创建 CameraCaptureSession、配置请求、处理回调——CameraX 全部帮你搞定。
你可能会问:「那它和 Camera2 的对应关系是怎样的?」 我刚开始也有这个疑惑。简单说,一个 Preview UseCase 背后就是一个 Camera2 的 CameraCaptureSession 重复请求,ImageCapture 对应 CaptureRequest 的 CONTROL_AE_PRECAPTURE 状态机。你用 CameraX 写代码时,其实是在操作更高层的语义,底层映射交给了框架。
1.3 核心组件详解
我把最常用的几个组件单独拿出来,说清楚它们分别干什么:
- ProcessCameraProvider —— 生命周期感知的 CameraX 入口。它像是一个单例管理器,负责把
Camera对象和 LifecycleOwner 绑定。我习惯在onCreate里就获取它的实例,避免重复创建。 - Preview —— 预览画面 UseCase。它接收一个
SurfaceProvider,你给它一个PreviewView或者自定义 Surface,它就持续往上面投数据。 - ImageCapture —— 拍照 UseCase。支持
takePicture()带回调,也可以保存到文件。注意,它默认会做 JPEG 压缩,你可以设置setCaptureMode(CaptureMode.MIN_LATENCY)来追求速度。 - ImageAnalysis —— 帧分析 UseCase。常用于二维码扫描、人脸检测等。它的回调频率可以调
setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST),保证不丢帧但不过载。 - CameraSelector —— 选择摄像头。默认
CameraSelector.DEFAULT_BACK_CAMERA和CameraSelector.DEFAULT_FRONT_CAMERA,你也可以根据镜头方向、感光特性自定义。
| UseCase | 输出类型 | 典型场景 | 性能敏感到? |
|---|---|---|---|
| Preview | Surface / TextureView | 取景框 | 帧率、分辨率切换 |
| ImageCapture | JPEG byte[] / File | 拍照 | 拍照延迟、HDR 处理 |
| ImageAnalysis | ImageProxy | AI 推理、扫码 | 分析频率、内存拷贝 |
1.4 生命周期绑定——自动管理的背后
我说一个真实的事。有个同事用 Camera2 写了一个扫码页面,测试时好好的,等放到线上,用户反馈「扫一两次就黑屏」。查了半天,原来是 Activity 重建时没有正确释放 Camera 资源。用 CameraX 以后,这种问题再也没出现过。
它怎么做到的?核心是 Lifecycle.addObserver()。你把 ProcessCameraProvider 绑定到当前页面的 Lifecycle,CameraX 内部监听 ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_DESTROY,自动调 Camera.open() 和 Camera.close()。你想想看,这得省多少模板代码。
// 一个典型的 CameraX 初始化
val processCameraProvider = ProcessCameraProvider.getInstance(this).get()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
val preview = Preview.Builder().build()
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
processCameraProvider.bindToLifecycle(
this, // LifecycleOwner
cameraSelector,
preview,
imageAnalysis
)
我的习惯: 把 bindToLifecycle 写在一个顶层函数里,传 LifecycleOwner 和 UseCase 列表。这样即使页面需要动态改变 UseCase(比如从预览切到拍照模式),也只需要重新 bind,旧的会自动解绑。
1.5 避坑指南——我踩过几次的坑
说两个我印象深的。
- 设备兼容性问题: 有些老机型(比如 API 21-23)的 Camera2 实现有 bug,CameraX 虽然内置了兼容层,但部分扩展效果(如 HDR、夜景)需要厂商支持。我曾经在低端机上跑
ImageCapture.Mode.MAX_QUALITY模式,结果拍照后卡住 2 秒。后来改成MIN_LATENCY就好了。 - UseCase 冲突: 同时绑定 Preview 和 ImageAnalysis 时,如果它们需要的分辨率或帧率不兼容,CameraX 会自动降级。有一次我把 Preview 设为最大 1080p,ImageAnalysis 设为 480p,结果预览始终只有 480p 清晰度。最后发现是 CameraX 为了省资源,取了两者的最小值。解决方案:手动配置
SupportedOutputSizes强制锁定。
特别注意: CameraX 默认会对 UseCase 做 ViewPort 裁剪,如果你要覆盖全屏预览,必须在 Preview.Builder 里设置 setTargetAspectRatio(WALL) 或者自定义 setTargetResolution()。否则你会看到黑边或画面被截断。
1.6 小结
CameraX 的架构可以总结为一句话:简化 Camera2 的复杂度,用 Lifecycle 自动管理相机状态。它把底层细节藏在 CameraXCore 中,开发者只需要关心「用哪个 UseCase」「输出什么画面」。接下来我们会深入到启动流程的每一段耗时,看看它到底慢在哪里、怎么优化。
本章要点速记:
- ProcessCameraProvider 是唯一入口,负责绑定 Lifecycle
- Preview / ImageCapture / ImageAnalysis 三大核心 UseCase
- CameraSelector 选择前后摄
- 自动生命周期管理:无惧重建、黑屏
- 兼容层解决大部分碎片化问题,但极端机型仍需兜底