17、CameraX:CameraX基础、图像分析、CameraX与Compose集成
各位同学,今天我们来聊聊CameraX。说实话,Android上的相机开发一直是个让人头疼的领域。早些年我用Camera1写过一个扫码功能,那代码量简直了——各种生命周期管理、旋转适配、预览尺寸计算……后来Google推出了Camera2,虽然功能强大了,但API设计得过于底层,我见过不少团队光是把预览画面调通就花了一周。
CameraX的出现,说白了就是Google终于意识到:大部分开发者只是想拍个照、扫个码,并不想跟CameraSession、CaptureRequest这些底层概念死磕。它基于Camera2封装,但提供了更友好的API。嗯,我个人习惯把它称为「相机界的Jetpack」——因为它同样解决了生命周期、配置一致性这些痛点。
核心要点:CameraX不是重新造轮子,而是把Camera2的复杂流程简化成几个UseCase(用例)。你只需要声明「我要预览」「我要拍照」「我要分析图像」,剩下的交给它。
17.1 CameraX基础:三个核心用例
CameraX把相机功能抽象成三个主要用例:Preview、ImageCapture、ImageAnalysis。它们可以独立使用,也可以组合使用。我在项目中用得最多的组合是Preview + ImageAnalysis,用来做实时扫码。
| 用例 | 功能 | 典型场景 |
|---|---|---|
| Preview | 显示相机预览画面 | 取景器、视频通话 |
| ImageCapture | 拍照并保存图片 | 普通拍照、文档扫描 |
| ImageAnalysis | 实时分析每一帧图像 | 二维码识别、人脸检测 |
先看一个最简单的集成示例。注意,这里我用的是ProcessCameraProvider——它是CameraX的核心入口,负责把用例绑定到生命周期上。
// 1. 获取 ProcessCameraProvider
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// 2. 创建 Preview 用例
val preview = Preview.Builder().build()
val previewView = findViewById<PreviewView>(R.id.previewView)
preview.setSurfaceProvider(previewView.surfaceProvider)
// 3. 创建 ImageCapture 用例
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// 4. 选择相机(默认后置)
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
// 5. 绑定到生命周期
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
imageCapture
)
}, ContextCompat.getMainExecutor(context))
这段代码看起来是不是很清爽?没有Camera2那些复杂的配置流程。你可能会问:为什么需要ProcessCameraProvider.getInstance()?因为CameraX内部需要异步初始化一些资源,比如相机硬件。我曾经遇到过在Activity的onCreate里直接调用导致崩溃的情况——嗯,后来养成了习惯,所有相机操作都放在addListener回调里。
17.2 图像分析:实时处理每一帧
图像分析是CameraX最强大的功能之一。它允许你以接近实时的速度处理每一帧图像数据。我做过一个项目,需要在扫码的同时检测画面亮度——用ImageAnalysis就非常合适。
实现起来很简单:创建一个ImageAnalysis用例,设置一个Analyzer,然后在analyze()方法里处理每一帧。
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { imageProxy ->
// imageProxy 包含当前帧的 YUV_420_888 格式数据
val buffer = imageProxy.planes[0].buffer
// ... 在这里做你的图像处理逻辑
// 重要:处理完后必须关闭
imageProxy.close()
})
注意:一定要在analyze()方法结束时调用imageProxy.close()。我曾经忘了关,结果相机预览卡死,排查了半天才发现是帧数据堆积导致的。CameraX的设计是:你不关,它就不给你下一帧。
关于setBackpressureStrategy,我个人建议大部分场景用STRATEGY_KEEP_ONLY_LATEST。为什么?因为图像分析通常不需要处理每一帧——比如扫码,你只需要最新的那一帧就够了。如果处理速度跟不上帧率,这个策略会自动丢弃旧帧,保证实时性。
17.3 CameraX与Compose集成
现在越来越多的项目用Compose写UI,CameraX当然也要跟上。好在Google官方提供了AndroidView这个桥接组件,可以把传统的PreviewView嵌入到Compose中。
我刚开始做Compose集成时踩过一个坑:PreviewView在Compose中需要手动管理生命周期。后来发现,用DisposableEffect配合LocalLifecycleOwner就能完美解决。
@Composable
fun CameraPreview(
modifier: Modifier = Modifier,
onImageCaptured: (Bitmap) -> Unit = {}
) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
// 保存 CameraProvider 实例,避免重组时重复创建
val cameraProvider = remember { MutableStateOf<ProcessCameraProvider?>(null) }
AndroidView(
modifier = modifier,
factory = { ctx ->
val previewView = PreviewView(ctx)
val cameraProviderFuture = ProcessCameraProvider.getInstance(ctx)
cameraProviderFuture.addListener({
val provider = cameraProviderFuture.get()
cameraProvider.value = provider
val preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
val imageCapture = ImageCapture.Builder().build()
provider.bindToLifecycle(
lifecycleOwner,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageCapture
)
}, ContextCompat.getMainExecutor(ctx))
previewView
},
update = { /* 这里可以更新配置 */ }
)
}
这段代码的核心思路是:用AndroidView承载PreviewView,在factory里完成CameraX的初始化。注意我用remember保存了cameraProvider,这样Compose重组时不会重复创建相机实例。
小技巧:如果你需要在Compose中切换前后摄像头,可以维护一个cameraSelector状态,然后在update回调里重新绑定。不过记得先unbind旧的用例,否则会报错。
17.4 知识体系总览
下面这张图总结了CameraX的核心架构和集成路径。我画图时特意把「生命周期绑定」放在中心位置——因为这是CameraX区别于Camera2的最大优势。
17.5 避坑指南与最佳实践
最后分享几个我在项目中踩过的坑,希望能帮你少走弯路。
- 不要在主线程做图像分析:
ImageAnalysis.Analyzer默认在后台线程执行,但如果你在analyze()里做了耗时操作(比如Bitmap转换),记得用setAnalyzer时传入自己的线程池。 - 预览尺寸适配:
PreviewView默认会自适应,但如果你发现画面拉伸,可以设置setScaleType为FILL_CENTER或FIT_CENTER。我习惯用FILL_CENTER,虽然会裁剪一点边缘,但不会变形。 - 权限问题:CameraX需要
CAMERA权限。记得在运行时请求,而且要在onResume之后才能绑定——否则会抛出SecurityException。 - 多用例冲突:某些设备不支持同时使用所有用例。比如低端机可能无法同时开启Preview和ImageAnalysis。可以用
CameraXConfig检查设备能力,或者降级处理。
总结一下:CameraX让Android相机开发变得前所未有的简单。你只需要关注业务逻辑——预览、拍照、分析——剩下的生命周期管理、设备兼容性,CameraX都帮你搞定了。我个人从Camera2迁移到CameraX后,相机相关的bug减少了至少80%。
好了,这一章的内容就到这里。记住,CameraX的核心就是「声明式用例 + 生命周期绑定」。把这个思路理清了,后面无论做什么相机功能都会很顺手。
公众号:蓝海资料掘金营,微信deep3321