Administrator
发布于 2026-08-25 / 25 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 42 篇:CameraX 实战:从 Camera2 到 CameraX 的迁移

上一篇聊了蓝牙 BLE,这篇继续硬件交互方向——相机。Camera2 API 强大但极其复杂,动辄几百行代码才能预览。CameraX 是 Jetpack 推出的封装层,大幅降低使用门槛。但很多存量项目还在用 Camera1 或 Camera2,迁移要注意什么?

为什么要从 Camera2 迁移到 CameraX

Camera2 的痛点:

  1. 模板代码多。打开相机要管 CameraManager、CameraDevice、CaptureSession、CaptureRequest 四层对象。

  2. 设备兼容性差。不同厂商实现 Camera2 HAL 的行为差异大,尤其是自动对焦、曝光、闪光灯。

  3. 生命周期管理复杂。camera open 是异步的,要处理各种回调,Activity 生命周期一变就乱。

CameraX 解决了这些问题:

  • 生命周期感知:绑定到 LifecycleOwner,自动管开关

  • 设备兼容层:内部处理不同厂商的差异

  • 声明式 API:用 UseCase 组合预览、拍照、分析

  • Camera2 互操作:可以通过 Camera2Interop 访问底层 Camera2 特性

基础预览

// 添加依赖
// implementation("androidx.camera:camera-core:1.4.0")
// implementation("androidx.camera:camera-camera2:1.4.0")
// implementation("androidx.camera:camera-lifecycle:1.4.0")
// implementation("androidx.camera:camera-view:1.4.0")

// 布局
// <androidx.camera.view.PreviewView android:id="@+id/previewView" />

class CameraFragment : Fragment() {
    private lateinit var previewView: PreviewView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        previewView = view.findViewById(R.id.previewView)
        startCamera()
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()

            val preview = Preview.Builder()
                .build()
                .also { it.setSurfaceProvider(previewView.surfaceProvider) }

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    viewLifecycleOwner, cameraSelector, preview
                )
            } catch (e: Exception) {
                Log.e("Camera", "绑定失败", e)
            }
        }, ContextCompat.getMainExecutor(requireContext()))
    }
}

对比 Camera2 的 200 行,CameraX 预览只要 20 行。

拍照

val imageCapture = ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
    .setTargetResolution(Size(1920, 1080))
    .build()

// 绑定到生命周期
cameraProvider.bindToLifecycle(
    viewLifecycleOwner, cameraSelector, preview, imageCapture
)

// 触发拍照
val photoFile = File(outputDirectory, "photo_${System.currentTimeMillis()}.jpg")
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

imageCapture.takePicture(
    outputOptions,
    ContextCompat.getMainExecutor(context),
    object : ImageCapture.OnImageSavedCallback {
        override fun onImageSaved(output: ImageCapture.OutputFileResults) {
            // 保存成功
        }
        override fun onError(exc: ImageCaptureException) {
            // 出错
        }
    }
)

图像分析

实时分析每一帧(用于二维码扫描、人脸检测等):

val imageAnalyzer = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()
    .also {
        it.setAnalyzer(executor) { imageProxy ->
            // 处理图像
            val buffer = imageProxy.planes[0].buffer
            val bytes = ByteArray(buffer.remaining())
            buffer.get(bytes)

            // 注意:ImageProxy 的格式是 YUV_420_888
            // 如果需要 Bitmap,需要转换

            imageProxy.close() // 必须关闭,否则不会收到下一帧
        }
    }

cameraProvider.bindToLifecycle(
    viewLifecycleOwner, cameraSelector, preview, imageCapture, imageAnalyzer
)

STRATEGY_KEEP_ONLY_LATEST 表示分析太慢时丢弃旧帧,只处理最新帧。如果需要处理每一帧用 BLOCK_PRODUCER,但会导致预览卡顿。

前后摄像头切换

private var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

fun switchCamera() {
    cameraSelector = if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
        CameraSelector.DEFAULT_FRONT_CAMERA
    } else {
        CameraSelector.DEFAULT_BACK_CAMERA
    }
    bindCamera() // 重新 bind
}

CameraX 自动处理摄像头切换,包括闪光灯、对焦模式等参数的重置。

从 Camera2 迁移

如果存量项目已经在用 Camera2,迁移策略:

  1. 渐进式替换。先在新增功能用 CameraX,老功能保持 Camera2。两者可以共存。

  2. Camera2Interop 扩展。如果需要 Camera2 特有的功能(如手动曝光),用 interop 访问:

val preview = Preview.Builder().also {
    Camera2Interop.Extender(it).setCaptureRequestOption(
        CaptureRequest.CONTROL_AE_MODE,
        CaptureRequest.CONTROL_AE_MODE_OFF
    )
}.build()
  1. Camera2Extension。CameraX 1.2+ 支持扩展模式(夜景、人像、美颜等),需要厂商支持。

兼容性注意

  1. CameraX 最低 API 21(Android 5.0),覆盖 99%+ 的设备。

  2. 部分老设备不支持某些 UseCase 组合。比如某些设备不支持同时预览 + 1080P 录制 + 图像分析。用 CameraProvider.hasCamera() 检查。

  3. PreviewView 的实现模式。IMPLEMENTATION_MODE_TEXTURE_VIEW 兼容性好但性能略差,SURFACE_VIEW 性能好但不支持某些叠加效果。

视频录制

CameraX 1.1+ 支持视频录制(VideoCapture):

val recorder = Recorder.Builder()
    .setQualitySelector(QualitySelector.from(Quality.HIGHEST))
    .build()

val videoCapture = VideoCapture.withOutput(recorder)

// 绑定
cameraProvider.bindToLifecycle(
    viewLifecycleOwner, cameraSelector, preview, videoCapture
)

// 开始录制
val mediaStoreOutput = MediaStoreOutputOptions.Builder(
    contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI
).build()

val activeRecording = videoCapture.output
    .prepareRecording(context, mediaStoreOutput)
    .start(Executor { command -> command.run() }) { event ->
        when (event) {
            is VideoRecordEvent.Status -> {
                // 录制中,可以获取时长和文件大小
            }
            is VideoRecordEvent.Finalize -> {
                if (event.hasError()) {
                    // 失败
                } else {
                    // 成功,获取 URI
                    val uri = event.outputResults.outputUri
                }
            }
        }
    }

小结

CameraX 大幅简化了相机开发的复杂度。新项目直接上 CameraX,存量 Camera2 项目按模块逐步迁移。核心思路是 UseCase 组合:预览 + 拍照 + 分析 + 录制按需绑定。别忘了处理权限和设备兼容性检查。

下一篇聊聊 Android Media3:ExoPlayer 的继任者。


评论