上一篇聊了蓝牙 BLE,这篇继续硬件交互方向——相机。Camera2 API 强大但极其复杂,动辄几百行代码才能预览。CameraX 是 Jetpack 推出的封装层,大幅降低使用门槛。但很多存量项目还在用 Camera1 或 Camera2,迁移要注意什么?
为什么要从 Camera2 迁移到 CameraX
Camera2 的痛点:
模板代码多。打开相机要管 CameraManager、CameraDevice、CaptureSession、CaptureRequest 四层对象。
设备兼容性差。不同厂商实现 Camera2 HAL 的行为差异大,尤其是自动对焦、曝光、闪光灯。
生命周期管理复杂。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,迁移策略:
渐进式替换。先在新增功能用 CameraX,老功能保持 Camera2。两者可以共存。
Camera2Interop 扩展。如果需要 Camera2 特有的功能(如手动曝光),用 interop 访问:
val preview = Preview.Builder().also {
Camera2Interop.Extender(it).setCaptureRequestOption(
CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_OFF
)
}.build()
Camera2Extension。CameraX 1.2+ 支持扩展模式(夜景、人像、美颜等),需要厂商支持。
兼容性注意
CameraX 最低 API 21(Android 5.0),覆盖 99%+ 的设备。
部分老设备不支持某些 UseCase 组合。比如某些设备不支持同时预览 + 1080P 录制 + 图像分析。用
CameraProvider.hasCamera()检查。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 的继任者。