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的最大优势。

CameraX 知识体系 ProcessCameraProvider Preview ImageCapture ImageAnalysis 绑定到 LifecycleOwner(自动管理打开/关闭) 传统 View:PreviewView Compose:AndroidView + PreviewView 核心思想:声明式用例 + 生命周期自动管理

17.5 避坑指南与最佳实践

最后分享几个我在项目中踩过的坑,希望能帮你少走弯路。

  • 不要在主线程做图像分析ImageAnalysis.Analyzer默认在后台线程执行,但如果你在analyze()里做了耗时操作(比如Bitmap转换),记得用setAnalyzer时传入自己的线程池。
  • 预览尺寸适配PreviewView默认会自适应,但如果你发现画面拉伸,可以设置setScaleTypeFILL_CENTERFIT_CENTER。我习惯用FILL_CENTER,虽然会裁剪一点边缘,但不会变形。
  • 权限问题:CameraX需要CAMERA权限。记得在运行时请求,而且要在onResume之后才能绑定——否则会抛出SecurityException
  • 多用例冲突:某些设备不支持同时使用所有用例。比如低端机可能无法同时开启Preview和ImageAnalysis。可以用CameraXConfig检查设备能力,或者降级处理。

总结一下:CameraX让Android相机开发变得前所未有的简单。你只需要关注业务逻辑——预览、拍照、分析——剩下的生命周期管理、设备兼容性,CameraX都帮你搞定了。我个人从Camera2迁移到CameraX后,相机相关的bug减少了至少80%。

好了,这一章的内容就到这里。记住,CameraX的核心就是「声明式用例 + 生命周期绑定」。把这个思路理清了,后面无论做什么相机功能都会很顺手。


公众号:蓝海资料掘金营,微信deep3321