CameraX 库解析:设计目标、UseCase 模型与生命周期绑定
各位同学,今天我们来聊聊 CameraX。说实话,这个库刚出来的时候,我内心是有点抵触的——又一套 API?后来真在项目里用上了,才觉得「真香」。CameraX 的设计初衷,说白了就是帮我们省掉那些重复的、容易出错的相机开发工作。
一、CameraX 的设计目标
Google 推出 CameraX,目标很明确:让相机开发变得简单、一致、可维护。我总结下来,核心就三点:
- 易用性:开发者不用再跟 Camera1 的繁琐回调、Camera2 的状态机死磕。几行代码就能跑起来预览。
- 生命周期感知:自动绑定 Activity/Fragment 的生命周期。你想想看,以前我们得手动在 onResume 打开相机、onPause 关闭相机,稍不留神就泄漏了。
- 设备兼容性:CameraX 内部做了大量兼容工作。我在项目中遇到过,同一款手机用 Camera2 拍照颜色偏绿,换 CameraX 就正常了——因为它自动应用了厂商的兼容补丁。
一句话总结:CameraX 是 Google 对 Camera2 的「上层封装」,目标是让 90% 的相机需求用最少的代码实现。
二、UseCase 模型:三个核心用例
CameraX 把相机功能抽象成几个独立的 UseCase。我个人习惯把 UseCase 理解为「相机能力的积木块」——你需要什么功能,就拼什么积木。
| UseCase | 功能 | 典型场景 |
|---|---|---|
| Preview | 取景预览 | 扫码界面、自拍预览 |
| ImageAnalysis | 逐帧图像分析 | 人脸检测、条形码识别 |
| VideoCapture | 视频录制 | 录像功能、直播推流 |
| ImageCapture | 拍照(静态图片) | 拍照按钮、连拍 |
这里有个关键点:多个 UseCase 可以同时绑定到同一个相机。比如你可以在预览的同时做图像分析,甚至同时录像。但要注意,不是所有组合都支持——比如某些低端机同时开 Preview + ImageAnalysis + VideoCapture 可能会卡顿。
我的经验:如果遇到组合不兼容,CameraX 会抛出异常。建议在初始化时用 CameraX.getAvailableUseCaseCombinations() 先查一下支持哪些组合。
三、生命周期绑定:再也不用担心泄漏了
以前用 Camera2,最头疼的就是生命周期管理。我记得有一次线上 bug,用户快速旋转屏幕导致相机崩溃,查了两天才发现是生命周期没处理好。
CameraX 的做法很聪明:它直接绑定到 LifecycleOwner。你只需要把 Activity 或 Fragment 传进去,剩下的打开、关闭、释放都由 CameraX 自动处理。
// 核心代码就这么几行
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
// 创建 Preview UseCase
val preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
// 绑定到生命周期
val camera = cameraProvider.bindToLifecycle(
lifecycleOwner, // 通常是 Activity 或 Fragment
CameraSelector.DEFAULT_BACK_CAMERA,
preview
)
}, ContextCompat.getMainExecutor(context))
看到了吗?bindToLifecycle 就是关键。当 Activity 走到 onDestroy 时,CameraX 会自动释放相机资源。你再也不用写那些又臭又长的 camera.open() 和 camera.release() 了。
注意:bindToLifecycle 必须在主线程调用。我曾经在子线程里调过,结果直接 crash 了。嗯,这个坑我替你们踩过了。
四、Preview:最简单的预览实现
Preview UseCase 负责把相机画面显示到屏幕上。它需要配合 PreviewView 使用——这是一个自定义 View,内部封装了 SurfaceView 和 TextureView 的切换逻辑。
// 在布局中
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
// 在代码中
val preview = Preview.Builder()
.setTargetResolution(Size(1920, 1080))
.build()
preview.setSurfaceProvider(previewView.surfaceProvider)
这里有个细节:setTargetResolution 不是强制分辨率,而是「建议分辨率」。CameraX 会选一个最接近的、设备支持的分辨率。我建议你设一个合理的值,别设 4K——很多低端机扛不住。
五、ImageAnalysis:逐帧分析的妙用
ImageAnalysis 是我个人用得最多的 UseCase。它可以把每一帧图像数据回调给你,让你做自定义处理。
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(executor, { imageProxy ->
// imageProxy 就是当前帧的数据
val buffer = imageProxy.planes[0].buffer
// 在这里做你的图像处理逻辑
imageProxy.close() // 记得关闭!
})
关键点:setBackpressureStrategy 有两种模式:STRATEGY_KEEP_ONLY_LATEST(只保留最新帧)和 STRATEGY_BLOCK_PRODUCER(阻塞直到处理完)。我建议用前者,否则处理慢的时候画面会卡顿。
另外,imageProxy.close() 一定要调。我曾经忘了调,结果内存泄漏,帧率越来越低。嗯,这个教训很深刻。
六、VideoCapture:录制视频的坑与解
VideoCapture 是相对较晚加入的 UseCase。它的用法和 Preview 类似,但多了个 VideoOutput 接口。
val videoCapture = VideoCapture.Builder()
.setBitRate(10_000_000) // 10 Mbps
.build()
// 开始录制
videoCapture.startRecording(
outputFileOptions,
ContextCompat.getMainExecutor(context),
object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
// 录制成功
}
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
// 录制失败
}
}
)
// 停止录制
videoCapture.stopRecording()
避坑指南:VideoCapture 和 ImageAnalysis 同时使用时,某些设备会报错。我建议先测试目标机型,或者用 CameraX.getAvailableUseCaseCombinations() 做兼容检查。
七、整体架构图
下面这张图展示了 CameraX 的核心架构。你可以看到,CameraX 位于应用层和 Camera2 之间,它屏蔽了底层的复杂性,向上提供简洁的 UseCase 接口。
从这张图可以清楚看到:应用层只跟 UseCase 打交道,UseCase 层把请求交给 CameraX 核心,核心再通过 Camera2 跟硬件通信。生命周期绑定和兼容性处理都在 CameraX 核心层完成,对开发者完全透明。
八、总结
CameraX 的设计哲学,说白了就是「把复杂留给自己,把简单留给开发者」。它用 UseCase 模型把相机功能拆解成独立模块,用生命周期绑定解决了最头疼的资源管理问题。
我个人建议:新项目直接上 CameraX,除非你有非常特殊的定制需求(比如 RAW 输出、高速连拍)。Camera2 虽然灵活,但维护成本太高了。你想想看,为了兼容几百款机型,Google 在 CameraX 里投入了多少人力?我们直接用现成的就好。
最后送大家一句话:能用 CameraX 解决的问题,就别自己造轮子。把精力放在业务逻辑上,它不香吗?
公众号:蓝海资料掘金营,微信deep3321