第1章:CameraX快速上手——添加依赖、配置PreviewView、实现Preview用例、实现ImageCapture用例

大家好,我是你们的老朋友。今天咱们来聊聊CameraX这个库。说实话,我第一次接触Android摄像头开发时,被Camera1那套回调机制折腾得够呛。后来Google推出了CameraX,我才觉得「嗯,这才是人干的事」。

CameraX说白了就是一个官方封好的摄像头工具包。它帮我们处理了生命周期、设备兼容性这些烦人的事情。你想想看,如果每个手机型号都要单独适配,那项目就别想上线了。

核心思想:CameraX = 摄像头能力 + 生命周期感知 + 设备兼容性。三者缺一不可。

1.1 添加依赖

咱们先从最基础的开始。在项目的 build.gradle(模块级别)里加上这几行:

dependencies {
    // CameraX 核心库
    def camerax_version = "1.3.1"
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"
    implementation "androidx.camera:camera-lifecycle:${camerax_version}"
    implementation "androidx.camera:camera-view:${camerax_version}"
}

这里我习惯把版本号单独拎出来,方便以后统一升级。记得同步一下Gradle,不然会报错。

小提示:如果你用的是Kotlin DSL,写法稍微有点不同,但核心依赖是一样的。我个人建议用最新稳定版,别追beta版,容易踩坑。

1.2 配置PreviewView

接下来是界面部分。我们需要一个 PreviewView 来显示摄像头画面。它本质上是一个特殊的View,专门用来渲染摄像头数据流。

在布局文件里这样写:

<androidx.camera.view.PreviewView
    android:id="@+id/previewView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

就这么简单?对,就这么简单。你不需要关心SurfaceView还是TextureView,CameraX帮你选好了。我曾经在项目里手动切换这两种View,那叫一个痛苦。现在好了,一个PreviewView全搞定。

1.3 实现Preview用例

现在我们来写核心代码。先获取一个 ProcessCameraProvider,它是CameraX的入口:

val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

cameraProviderFuture.addListener({
    val cameraProvider = cameraProviderFuture.get()
    
    // 创建Preview用例
    val preview = Preview.Builder().build()
    
    // 把PreviewView的surface provider传进去
    preview.setSurfaceProvider(previewView.surfaceProvider)
    
    // 绑定到生命周期
    val camera = cameraProvider.bindToLifecycle(
        this,          // LifecycleOwner
        CameraSelector.DEFAULT_BACK_CAMERA,  // 后置摄像头
        preview        // 预览用例
    )
}, ContextCompat.getMainExecutor(this))

这里有个关键点:bindToLifecycle 会把摄像头和Activity/Fragment的生命周期绑定。页面可见时自动打开摄像头,不可见时自动释放。你不用再写 onResumeonPause 了,省心不少。

注意:记得在AndroidManifest.xml里声明摄像头权限:

<uses-permission android:name="android.permission.CAMERA" />

运行时也要动态申请权限,不然会直接崩溃。我刚开始就忘了这茬,调试了半天。

1.4 实现ImageCapture用例

光能看还不够,咱们还得能拍照。ImageCapture就是干这个的:

// 创建ImageCapture用例
val imageCapture = ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
    .build()

// 绑定到生命周期(和Preview一起)
cameraProvider.bindToLifecycle(
    this,
    CameraSelector.DEFAULT_BACK_CAMERA,
    preview,
    imageCapture   // 多个用例用逗号隔开
)

// 拍照
imageCapture.takePicture(
    ContextCompat.getMainExecutor(this),
    object : ImageCapture.OnImageCapturedCallback() {
        override fun onCaptureSuccess(image: ImageProxy) {
            // 处理拍到的图片
            // image.image 就是原始的YUV_420_888数据
        }

        override fun onError(exception: ImageCaptureException) {
            // 处理错误
        }
    }
)

看到没?拍照和预览可以同时绑定。CameraX会自动协调这两个用例,不会冲突。我以前用Camera2的时候,拍照和预览要手动切换,稍不注意就黑屏了。

避坑指南:我曾经在 onCaptureSuccess 里直接保存图片,结果发现图片方向不对。后来加了 setTargetRotation 才解决。建议拍照前设置一下屏幕旋转:

val imageCapture = ImageCapture.Builder()
    .setTargetRotation(previewView.display.rotation)
    .build()

1.5 知识体系总览

为了让你更直观地理解CameraX的架构,我画了一张图:

CameraX 架构图 你的应用(Activity / Fragment) CameraX 核心 Preview 预览 ImageCapture 拍照 ImageAnalysis 分析 Camera2 API(系统底层) 摄像头硬件

从图里可以看出来,CameraX位于应用和底层Camera2之间。它把复杂的Camera2 API封装成了三个简单的用例:预览、拍照、分析。你只需要告诉它「我要干什么」,剩下的交给它处理。

1.6 完整示例代码

最后,我把上面所有代码整合成一个完整的Activity。你直接复制就能跑:

class CameraActivity : AppCompatActivity() {
    private lateinit var previewView: PreviewView
    private var imageCapture: ImageCapture? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera)
        previewView = findViewById(R.id.previewView)
        
        // 请求权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 100)
        } else {
            startCamera()
        }
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            
            val preview = Preview.Builder().build()
            preview.setSurfaceProvider(previewView.surfaceProvider)
            
            imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .setTargetRotation(previewView.display.rotation)
                .build()
            
            cameraProvider.bindToLifecycle(
                this,
                CameraSelector.DEFAULT_BACK_CAMERA,
                preview,
                imageCapture
            )
        }, ContextCompat.getMainExecutor(this))
    }

    fun takePhoto() {
        imageCapture?.takePicture(
            ContextCompat.getMainExecutor(this),
            object : ImageCapture.OnImageCapturedCallback() {
                override fun onCaptureSuccess(image: ImageProxy) {
                    // 保存或处理图片
                }
                override fun onError(exception: ImageCaptureException) {
                    Log.e("Camera", "拍照失败", exception)
                }
            }
        )
    }
}

嗯,到这里CameraX的基础用法就讲完了。你可能会觉得「就这么点东西?」对,就这么点。CameraX的设计初衷就是让摄像头开发变得简单。你不需要理解复杂的管道、会话、请求,只需要关注业务逻辑。

下一章我们会深入ImageAnalysis用例,看看怎么实时分析每一帧画面。不过那是后话了,先把今天的内容消化掉。


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