CameraX 相机开发:从生命周期到 AI 集成的完整实践
大家好,今天我们来聊聊 CameraX。说实话,Android 相机开发曾经是个让人头疼的领域。我记得刚入行那会儿,为了写一个简单的拍照功能,得跟 Camera1 的 SurfaceView、各种回调、还有不同厂商的兼容性问题死磕。后来 Google 推出了 CameraX,嗯,这玩意儿确实让事情变得简单多了。
CameraX 的核心思想是什么?说白了,就是帮你把相机生命周期和 Android 组件生命周期绑定在一起。你不用再手动管理相机的打开、关闭、释放这些琐事。你只需要关注业务逻辑——拍照、预览、分析图像。剩下的,CameraX 帮你搞定。
核心要点:CameraX 是一个 Jetpack 组件,它通过 ProcessCameraProvider 与 LifecycleOwner 绑定,自动处理相机的生命周期。你不需要再写 onResume 里打开相机、onPause 里释放相机这种模板代码。
生命周期绑定:让相机跟着 Activity/Fragment 走
我们先来看一个最简单的例子。假设你要在 Activity 里集成相机预览,传统做法你得写多少代码?我算过,至少 200 行。用 CameraX 呢?
// 1. 获取 ProcessCameraProvider
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// 2. 创建 Preview 用例
val preview = Preview.Builder().build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
// 3. 绑定到生命周期
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, // LifecycleOwner
cameraSelector,
preview
)
} catch (e: Exception) {
Log.e(TAG, "绑定失败", e)
}
}, ContextCompat.getMainExecutor(this))
看到没?核心就三步:获取 Provider、创建用例、绑定生命周期。bindToLifecycle 这个方法一调用,CameraX 就会自动监听 Activity 的 onStart、onStop、onDestroy 事件。你 Activity 销毁了,相机资源自动释放。我曾经在项目里见过有人忘了释放相机导致内存泄漏,用 CameraX 之后这种问题再也没出现过。
个人习惯:我一般会在 Application 初始化时预加载 ProcessCameraProvider,这样进入相机页面时能快 200-300ms。不过要注意,预加载会增加内存占用,权衡一下。
三大核心用例:Preview、ImageCapture、ImageAnalysis
CameraX 提供了三个开箱即用的用例。你可以单独用,也可以组合用。我画了一张图,帮你理清它们的关系:
Preview 用例:让用户看到画面
Preview 用例负责把相机画面渲染到屏幕上。你需要提供一个 PreviewView(CameraX 提供的专用 View),然后通过 setSurfaceProvider 关联起来。
// 布局文件中的 PreviewView
// <androidx.camera.view.PreviewView
// android:id="@+id/viewFinder"
// android:layout_width="match_parent"
// android:layout_height="match_parent" />
// 代码中绑定
val preview = Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.build()
preview.setSurfaceProvider(viewFinder.surfaceProvider)
这里有个坑,我踩过。setTargetAspectRatio 设置的是期望比例,不是强制比例。如果设备不支持 16:9,CameraX 会自动选一个最接近的。你想想看,如果你强制要求 4:3,有些全面屏手机画面会被拉伸,用户体验很差。所以我建议用 RATIO_16_9,兼容性最好。
ImageCapture 用例:拍照并保存
拍照功能,CameraX 封装得非常优雅。你只需要调用 takePicture 方法,传入一个 OnImageCapturedCallback 或者一个文件保存路径。
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// 拍照到内存
imageCapture.takePicture(
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
// 处理 image 对象
// 注意:用完必须调用 image.close() 释放
}
override fun onError(exception: ImageCaptureException) {
Log.e(TAG, "拍照失败: ${exception.imageCaptureError}")
}
}
)
// 拍照到文件
val photoFile = File(externalMediaDirs.first(),
"IMG_${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) {
// 文件已保存
}
override fun onError(exception: ImageCaptureException) {
// 处理错误
}
}
)
注意:ImageProxy 对象使用后必须调用 close(),否则会导致相机管道阻塞。我曾经在代码审查时发现有人忘了 close,结果拍几张照片后相机就卡死了。建议用 use 扩展函数自动释放。
ImageAnalysis 用例:逐帧分析
这个用例特别适合做实时图像处理,比如扫码、人脸检测、滤镜等。它会把每一帧图像数据通过 Analyzer 回调给你。
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor()) { imageProxy ->
// imageProxy 是每一帧的图像数据
val buffer = imageProxy.planes[0].buffer
// 在这里做你的图像处理逻辑
// 处理完后必须关闭
imageProxy.close()
}
关于背压策略,我多说两句。STRATEGY_KEEP_ONLY_LATEST 会丢弃旧帧,只保留最新一帧。如果你的分析逻辑比较耗时,这个策略能保证你不会被帧数据淹没。STRATEGY_BLOCK_PRODUCER 则会阻塞生产者,直到你处理完当前帧。我个人习惯用 KEEP_ONLY_LATEST,因为实时性更重要,丢几帧无所谓。
相机配置与扩展:HDR、夜景模式
CameraX 通过 CameraXConfig 和 ExtensionsManager 来支持厂商特定的功能。比如 HDR 和夜景模式,这些功能依赖硬件和驱动支持。
// 检查并启用 HDR
val extensionsManager = ExtensionsManager.getInstance(context)
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
if (extensionsManager.isExtensionAvailable(
cameraProvider,
cameraSelector,
ExtensionMode.HDR
)) {
val hdrCameraSelector = extensionsManager.getExtensionEnabledCameraSelector(
cameraSelector,
ExtensionMode.HDR
)
cameraProvider.bindToLifecycle(
this,
hdrCameraSelector,
preview,
imageCapture
)
}
// 夜景模式同理,把 ExtensionMode.HDR 换成 ExtensionMode.NIGHT
这里要注意,不是所有设备都支持这些扩展。我测试过,Pixel 系列支持得最好,一些国产中低端手机可能不支持。所以一定要先调用 isExtensionAvailable 检查,否则会 crash。
CameraX 与 ML Kit 集成:让相机拥有 AI 能力
ML Kit 是 Google 的机器学习套件,它和 CameraX 的 ImageAnalysis 用例是天作之合。你只需要把 ImageProxy 转换成 ML Kit 需要的 InputImage 格式,然后调用对应的检测器即可。
下面是一个实时人脸检测的例子:
// 创建人脸检测器
val options = FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
.build()
val faceDetector = FaceDetection.getClient(options)
// 在 ImageAnalysis.Analyzer 中集成
imageAnalysis.setAnalyzer(executor) { imageProxy ->
@Suppress("UNCHECKED_CAST")
val mediaImage = imageProxy.image
if (mediaImage != null) {
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
faceDetector.process(inputImage)
.addOnSuccessListener { faces ->
// 处理检测到的人脸
for (face in faces) {
val bounds = face.boundingBox
val smileProb = face.smilingProbability
// 绘制人脸框、添加贴纸等
}
}
.addOnCompleteListener {
// 必须关闭 imageProxy
imageProxy.close()
}
} else {
imageProxy.close()
}
}
ML Kit 支持的场景很多:条码扫描、文字识别、对象检测、图像标注等。我曾在电商项目里用 CameraX + ML Kit 做商品条码扫描,用户体验比传统 ZXing 库好很多,因为 CameraX 自动处理了旋转和缩放。
性能优化建议:ML Kit 的分析任务建议放在独立线程池,不要占用主线程。另外,如果检测器返回结果太慢,可以降低 ImageAnalysis 的分辨率,比如用 setTargetResolution(Size(640, 480))。分辨率越低,处理越快。
组合使用:一个完整的相机页面
最后,我们看看如何把三个用例组合在一起。实际项目中,你通常需要同时支持预览、拍照和分析。
val preview = Preview.Builder().build()
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.build()
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
// 绑定所有用例
cameraProvider.bindToLifecycle(
this,
cameraSelector,
preview,
imageCapture,
imageAnalysis
)
bindToLifecycle 方法可以接受多个用例。CameraX 会自动判断设备是否支持同时运行这些用例。如果不支持,它会抛出异常。我建议在 bind 之前先检查一下:
val available = cameraProvider.hasCamera(cameraSelector)
if (!available) {
// 降级处理,比如只绑定 Preview 和 ImageCapture
}
好了,CameraX 的核心内容就这些。从生命周期绑定到三大用例,再到扩展功能和 ML Kit 集成,你会发现 CameraX 的设计思路就是「让复杂的事情变简单」。你不需要成为相机专家,也能写出稳定、高效的相机应用。嗯,这就是 Jetpack 组件存在的意义。
公众号:蓝海资料掘金营,微信deep3321