CameraX 简介:设计理念、生命周期感知与四大用例
大家好,我是你们这节课的讲师。今天我们来聊聊 CameraX。
说实话,Android 的摄像头开发,在 CameraX 出来之前,确实有点「劝退」。我自己早年做项目时,光是处理摄像头旋转、生命周期、不同厂商的兼容性问题,就熬了好几个通宵。后来 Google 推出了 CameraX,我第一时间就试用了。嗯,怎么说呢,它把很多脏活累活都包了,让我能更专注于业务逻辑。
这一章,我们就来拆解 CameraX 的核心。我会从设计理念讲起,然后带你理解它的生命周期感知,最后把四个核心用例(Preview、ImageCapture、VideoCapture、ImageAnalysis)逐个说清楚。
一、CameraX 的设计理念:让摄像头开发变简单
CameraX 的设计初衷,说白了就是一句话:降低复杂度,提高一致性。
你想想看,以前用 Camera2 API 写一个拍照功能,要处理多少东西?
- 要自己管理 CameraManager、CameraDevice、CameraCaptureSession
- 要处理各种 Surface 的创建和销毁
- 要适配不同厂商的奇葩行为
CameraX 把这些都封装起来了。它采用了一种「用例(Use Case)」驱动的架构。什么意思呢?就是你只需要告诉 CameraX:「我要预览」、「我要拍照」、「我要录像」,它就会自动帮你配置好底层的 Camera2 流程。
核心设计理念:
- 易用性:几行代码就能实现预览和拍照
- 生命周期感知:自动绑定 Activity/Fragment 的生命周期
- 设备兼容性:Google 做了大量兼容性测试,覆盖了市面上绝大多数设备
- 可扩展性:支持自定义扩展,比如美颜、HDR
我个人习惯把 CameraX 比作一个「智能管家」。你告诉它你要做什么,它就去协调底层硬件,你只需要关注结果就行。
二、生命周期感知:再也不用担心 Activity 销毁了
这是 CameraX 最让我喜欢的一个特性。以前用 Camera2,我必须在 onResume() 里打开摄像头,在 onPause() 里关闭,稍有不慎就会导致内存泄漏或者摄像头被占用。
CameraX 通过 ProcessCameraProvider 和 LifecycleOwner 实现了自动绑定。你只需要把 Activity 或 Fragment 的生命周期传给它,它就会自动在合适的时机打开和关闭摄像头。
举个例子:
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// 绑定生命周期
val camera = cameraProvider.bindToLifecycle(
this, // LifecycleOwner
cameraSelector,
previewUseCase,
imageCaptureUseCase
)
}, ContextCompat.getMainExecutor(this))
看到了吗?bindToLifecycle 方法把摄像头和当前页面的生命周期绑定了。当页面销毁时,CameraX 会自动释放摄像头资源。我曾经在一个项目里忘记释放 Camera2 的 session,结果导致其他应用无法调用摄像头,用户直接给了差评。用 CameraX 之后,这种低级错误再也没犯过。
小提示: 如果你在 Fragment 中使用 CameraX,记得把 LifecycleOwner 传成 viewLifecycleOwner,而不是 Fragment 本身。这样可以避免 Fragment 视图销毁时摄像头还占用着。
三、四大用例详解
CameraX 提供了四个开箱即用的用例。它们可以单独使用,也可以组合使用。下面我逐个介绍。
1. Preview(预览)
预览是最基本的功能。CameraX 的 Preview 用例需要配合 PreviewView 使用。你只需要创建一个 Preview 对象,设置一个 SurfaceProvider,然后绑定到生命周期即可。
val preview = Preview.Builder()
.build()
preview.setSurfaceProvider(previewView.surfaceProvider)
cameraProvider.bindToLifecycle(
this,
cameraSelector,
preview
)
这里要注意一点:PreviewView 是一个自定义的 View,它内部处理了 Surface 的创建和显示。你不需要再像以前那样自己写 TextureView 或 SurfaceView 了。
2. ImageCapture(拍照)
拍照用例也很简单。你创建一个 ImageCapture 对象,然后调用它的 takePicture() 方法。
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// 拍照
imageCapture.takePicture(
context,
executor,
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
// 照片保存成功
}
override fun onError(exception: ImageCaptureException) {
// 处理错误
}
}
)
我建议你在设置 CaptureMode 时,根据场景选择:
CAPTURE_MODE_MINIMIZE_LATENCY:优先保证快门速度,适合抓拍CAPTURE_MODE_MAXIMIZE_QUALITY:优先保证画质,适合静物拍摄
避坑指南: 我曾经在低端机上使用 MAXIMIZE_QUALITY 模式,结果拍照后处理时间过长,导致 ANR。后来我改成 MINIMIZE_LATENCY,并在后台异步保存图片,问题就解决了。所以,如果你的应用需要快速响应,建议用延迟模式。
3. VideoCapture(录像)
录像用例稍微复杂一点,但 CameraX 也做了很好的封装。你需要创建一个 VideoCapture 对象,并设置一个 Recorder。
val recorder = Recorder.Builder()
.setQualitySelector(QualitySelector.from(Quality.HIGHEST))
.build()
val videoCapture = VideoCapture.Builder(recorder)
.build()
// 开始录像
val mediaStoreOutput = MediaStoreOutputOptions.Builder(
contentResolver,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
)
.build()
videoCapture.output.prepareRecording(context, mediaStoreOutput)
.start(ContextCompat.getMainExecutor(this)) { recordEvent ->
// 录像状态回调
}
// 停止录像
videoCapture.stopRecording()
这里有个细节:Recorder 是单独配置的,你可以设置视频质量、帧率等参数。我个人习惯把 Quality.HIGHEST 作为默认值,但如果用户存储空间不足,我会降级到 Quality.SD。
4. ImageAnalysis(图像分析)
这个用例是给开发者做实时图像处理用的,比如二维码扫描、人脸检测。它会把每一帧图像数据回调给你。
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(executor) { imageProxy ->
// 处理每一帧图像
val buffer = imageProxy.planes[0].buffer
// ... 你的分析逻辑
// 处理完后一定要关闭
imageProxy.close()
}
这里有一个非常重要的点:一定要调用 imageProxy.close()。否则 CameraX 会认为你还在处理上一帧,导致后续帧被阻塞。我见过不少新手在这里踩坑,图像分析越来越卡,最后直接崩溃。
关于背压策略:
STRATEGY_KEEP_ONLY_LATEST:只保留最新的一帧,适合实时性要求高的场景(比如扫码)STRATEGY_BLOCK_PRODUCER:阻塞生产者,直到当前帧处理完毕,适合需要处理每一帧的场景(比如视频滤镜)
四、知识体系总览
为了让你更直观地理解 CameraX 的整体架构,我画了一张图。你可以看到,ProcessCameraProvider 是核心,它管理着生命周期和所有用例。
从这张图你可以看到,ProcessCameraProvider 是中心枢纽。它接收 LifecycleOwner 的生命周期信号,然后协调四个用例的工作。底层虽然还是 Camera2,但开发者完全不需要关心那些细节。
五、组合使用:一个完整的例子
在实际项目中,我们通常会把 Preview 和 ImageCapture 组合在一起。比如做一个扫码应用,可能还需要加上 ImageAnalysis。下面是一个同时使用 Preview 和 ImageCapture 的代码片段:
class CameraActivity : AppCompatActivity() {
private lateinit var previewView: PreviewView
private lateinit var imageCapture: ImageCapture
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
previewView = findViewById(R.id.previewView)
startCamera()
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// 创建预览用例
val preview = Preview.Builder()
.build()
.also { it.setSurfaceProvider(previewView.surfaceProvider) }
// 创建拍照用例
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// 选择后置摄像头
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// 绑定到生命周期
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this,
cameraSelector,
preview,
imageCapture
)
} catch (e: Exception) {
Log.e("CameraX", "绑定失败", e)
}
}, ContextCompat.getMainExecutor(this))
}
fun takePhoto() {
val photoFile = File(
externalMediaDirs.first(),
"${System.currentTimeMillis()}.jpg"
)
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
Log.d("CameraX", "照片保存成功: ${photoFile.absolutePath}")
}
override fun onError(exception: ImageCaptureException) {
Log.e("CameraX", "拍照失败", exception)
}
}
)
}
}
这段代码里,我同时绑定了 Preview 和 ImageCapture 两个用例。当用户点击拍照按钮时,调用 takePhoto() 方法即可。整个过程不需要手动打开或关闭摄像头,CameraX 会自动处理。
总结一下 CameraX 的优势:
- 代码量减少 60% 以上
- 自动处理生命周期,避免内存泄漏
- 内置设备兼容性解决方案
- 用例可自由组合,灵活度高
好了,这一章的内容就到这里。CameraX 的设计理念和四个用例,你掌握了吗?下一章我们会深入每个用例的配置细节,包括分辨率选择、闪光灯控制、对焦模式等。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。