1. 为什么选择CameraX作为自定义相机方案
在移动端开发中,相机功能实现一直是个既基础又复杂的课题。传统Camera API的开发就像在悬崖边行走——稍有不慎就会遇到兼容性问题、生命周期管理混乱或者性能瓶颈。三年前我在一个电商项目中使用Camera2 API实现扫码功能时,就曾因为不同厂商设备的预览方向处理差异,导致测试阶段出现了27种不同的异常情况。
CameraX的出现改变了这种局面。这个Jetpack组件本质上是对Camera2 API的现代化封装,它解决了三个核心痛点:
- 设备兼容性:自动处理不同厂商的硬件差异
- 生命周期感知:与Activity/Fragment生命周期自动同步
- 用例抽象:将相机功能分解为预览、图像分析和拍照三个标准化用例
重要提示:虽然CameraX最低支持到API 21,但建议将minSdkVersion设为24(Android 7.0)以获得完整的特性支持。这是我们在实际项目中验证过的稳定性分水岭。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础环境搭建与配置
2.1 依赖项配置
在模块级build.gradle中添加以下依赖(以当前稳定版1.3.0为例):
groovy复制dependencies {
def camerax_version = "1.3.0"
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:${camera_version}"
// 如需扩展功能(如人脸检测)
implementation "androidx.camera:camera-extensions:${camera_version}"
}
2.2 权限声明
AndroidManifest.xml中需要声明相机和存储权限:
xml复制<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<!-- 如果需要保存照片 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
注意存储权限的maxSdkVersion设置:从Android 10开始推荐使用MediaStore API,无需声明存储权限。
3. 核心功能实现详解
3.1 预览视图搭建
CameraX的PreviewView是一个神奇的组件,它自动处理了SurfaceTexture的创建和尺寸变换。以下是正确使用姿势:
kotlin复制// 在布局XML中添加
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:scaleType="fillCenter" /> // 关键属性:fillCenter/fitStart/fitEnd
// 代码中配置
val preview = Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setTargetRotation(previewView.display.rotation)
.build()
preview.setSurfaceProvider(previewView.surfaceProvider)
避坑指南:scaleType的选择直接影响预览效果:
- fillCenter:填充整个视图,可能裁剪图像
- fitStart/fitEnd:保持原比例,可能留有黑边
3.2 拍照功能实现
高阶拍照配置示例(包含EXIF信息写入):
kotlin复制val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setBufferFormat(ImageFormat.JPEG)
.setFlashMode(ImageCapture.FLASH_MODE_AUTO)
.setTargetRotation(previewView.display.rotation)
.build()
// 拍照执行
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file)
.setMetadata(ImageCapture.Metadata().apply {
// 写入地理位置信息(需要额外权限)
isLocationEnabled = true
// 自定义EXIF标签
setReversedExif(ExifInterface.TAG_MAKE, "MyCameraApp")
})
.build()
imageCapture.takePicture(
outputFileOptions,
ContextCompat.getMainExecutor(requireContext()),
object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
// 错误处理(建议记录失败率监控)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
// 成功回调(可添加图片到MediaStore)
}
}
)
3.3 图像分析进阶技巧
图像分析用例可以实现实时滤镜、二维码识别等功能。这里展示一个YUV转RGB的优化方案:
kotlin复制val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.build()
imageAnalysis.setAnalyzer(executor) { imageProxy ->
// 直接获取RGBA格式数据(避免手动转换)
val rgbaBuffer = imageProxy.planes[0].buffer
val width = imageProxy.width
val height = imageProxy.height
// 使用RenderScript进行高效图像处理(示例:边缘检测)
val rs = RenderScript.create(requireContext())
val input = Allocation.createFromBitmap(rs, bitmap)
val output = Allocation.createTyped(rs, input.type)
ScriptIntrinsicConvolve3x3.create(rs, Element.U8_4(rs)).apply {
setCoefficients(floatArrayOf(
-1f, 0f, 1f,
-2f, 0f, 2f,
-1f, 0f, 1f
))
forEach(input, output)
}
output.copyTo(bitmap)
imageProxy.close() // 必须手动关闭!
}
性能提示:STRATEGY_KEEP_ONLY_LATEST策略适合实时性要求高的场景,如果要做帧序列分析(如动作识别),应改用STRATEGY_BLOCK_PRODUCER。
4. 高级功能与性能优化
4.1 多摄像头切换策略
现代设备往往配备多个摄像头,CameraX提供了优雅的切换方案:
kotlin复制val cameraSelector = when (cameraLensFacing) {
CameraSelector.LENS_FACING_FRONT -> CameraSelector.DEFAULT_FRONT_CAMERA
CameraSelector.LENS_FACING_BACK -> CameraSelector.DEFAULT_BACK_CAMERA
else -> CameraSelector.DEFAULT_BACK_CAMERA
}
// 检查设备是否支持该摄像头
val hasCamera = ProcessCameraProvider.getInstance(requireContext())
.await().hasCamera(cameraSelector)
// 切换摄像头时先解绑再重新绑定
cameraProvider.unbindAll()
try {
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalysis
)
} catch(exc: Exception) {
// 处理异常(如摄像头被占用)
}
4.2 低延迟拍照优化
对于需要极速响应的场景(如证件拍摄),可以采用以下优化组合:
- 配置拍照用例:
kotlin复制ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setFlashMode(ImageCapture.FLASH_MODE_OFF) // 关闭闪光灯计算
.setBufferFormat(ImageFormat.YUV_420_888) // 原始格式更快
.build()
- 使用内存缓存替代文件存储:
kotlin复制imageCapture.takePicture(executor, object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
// 直接处理YUV数据
val yBuffer = image.planes[0].buffer
val uBuffer = image.planes[1].buffer
val vBuffer = image.planes[2].buffer
// ...图像处理逻辑
image.close()
}
})
4.3 扩展功能集成
CameraX的扩展模块提供了一些开箱即用的特效:
kotlin复制// 检查设备是否支持某特效
val extensions = cameraProvider.availableCameraInfos
.map { info -> CameraX.getExtensionsManager(requireContext(), info) }
.firstOrNull { it.isExtensionAvailable(CameraSelector.LENS_FACING_BACK,
ExtensionMode.BOKEH) }
// 启用背景虚化模式
val bokehSelector = extensions?.getExtensionEnabledCameraSelector(
CameraSelector.LENS_FACING_BACK,
ExtensionMode.BOKEH
)
cameraProvider.bindToLifecycle(
this,
bokehSelector ?: CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageCapture
)
5. 实战中的疑难问题解决
5.1 预览方向异常问题
这是最常见的兼容性问题之一,完整解决方案如下:
- 检查设备物理传感器方向:
kotlin复制val sensorOrientation = cameraProvider.availableCameraInfos
.first { it.lensFacing == CameraSelector.LENS_FACING_BACK }
.getSensorRotationDegrees(relativeRotation)
- 动态调整预览方向:
kotlin复制previewView.post {
val rotation = when(previewView.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> 0
}
preview.targetRotation = rotation
imageCapture.targetRotation = rotation
imageAnalysis.targetRotation = rotation
}
5.2 内存泄漏预防措施
CameraX虽然自带生命周期感知,但仍需注意:
- 在Fragment中正确释放资源:
kotlin复制override fun onDestroyView() {
super.onDestroyView()
cameraProvider.unbindAll()
imageAnalysis.clearAnalyzer()
}
- 图像分析器的线程管理:
kotlin复制// 使用自定义的协程作用域
private val analysisScope = CoroutineScope(
SupervisorJob() + Dispatchers.Default
)
imageAnalysis.setAnalyzer(executor) { imageProxy ->
analysisScope.launch {
// 处理逻辑
imageProxy.close()
}
}
override fun onDestroy() {
analysisScope.cancel()
super.onDestroy()
}
5.3 低端设备适配方案
针对低配设备的优化策略:
- 降低分辨率配置:
kotlin复制val resolutionSelector = ResolutionSelector.Builder()
.setAspectRatioStrategy(AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY)
.setResolutionStrategy(
ResolutionStrategy(
Size(1280, 720), // 目标分辨率
ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER
)
)
.build()
Preview.Builder()
.setResolutionSelector(resolutionSelector)
.build()
- 启用硬件加速解码:
kotlin复制ImageAnalysis.Builder()
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
.setHardwareBuffersEnabled(true) // API 29+
.build()
6. 扩展功能开发思路
6.1 自定义图像处理管线
结合OpenCV实现高级效果:
kotlin复制imageAnalysis.setAnalyzer(executor) { imageProxy ->
val mat = Mat(imageProxy.height + imageProxy.height/2,
imageProxy.width,
CvType.CV_8UC1).apply {
put(0, 0, imageProxy.planes[0].buffer.array()) // Y
put(imageProxy.height, 0, imageProxy.planes[1].buffer.array()) // U
put(imageProxy.height + imageProxy.height/4, 0,
imageProxy.planes[2].buffer.array()) // V
}
// 转换为RGB格式
val rgbMat = Mat()
Imgproc.cvtColor(mat, rgbMat, Imgproc.COLOR_YUV2RGB_NV21)
// 示例:人脸检测
val faceDetector = CascadeClassifier.load(resources.openRawResource(R.raw.haarcascade))
val faces = MatOfRect()
faceDetector.detectMultiScale(rgbMat, faces)
imageProxy.close()
}
6.2 视频录制功能扩展
虽然CameraX主要面向静态图像,但可以结合MediaCodec实现录制:
kotlin复制val mediaRecorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(createTempFile("video", ".mp4").absolutePath)
setVideoEncodingBitRate(5_000_000)
setVideoFrameRate(30)
setVideoSize(1280, 720)
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
prepare()
}
val videoCapture = VideoCapture.withOutput(mediaRecorder)
cameraProvider.bindToLifecycle(this, cameraSelector, preview, videoCapture)
// 开始录制
mediaRecorder.start()
// 停止录制
mediaRecorder.stop()
mediaRecorder.reset()
6.3 与ML Kit的深度集成
实现实时物体识别的最佳实践:
kotlin复制val options = ObjectDetectorOptions.Builder()
.setDetectorMode(ObjectDetectorOptions.STREAM_MODE)
.enableClassification()
.build()
val objectDetector = ObjectDetection.getClient(options)
imageAnalysis.setAnalyzer(executor) { imageProxy ->
val image = InputImage.fromMediaImage(
imageProxy.image!!,
imageProxy.imageInfo.rotationDegrees
)
objectDetector.process(image)
.addOnSuccessListener { results ->
// 处理识别结果
}
.addOnCompleteListener {
imageProxy.close()
}
}
在实现自定义相机的过程中,最深刻的体会是:CameraX虽然大幅降低了开发门槛,但要做出真正专业级的相机应用,仍然需要深入理解图像处理原理和设备硬件特性。特别是在处理不同厂商的设备兼容性问题时,建立完善的设备信息收集和异常监控机制至关重要——我们团队为此专门开发了一套设备指纹系统,记录各种异常场景的解决方案,这比任何官方文档都更实用。
