1. Android Camera开发概述
在移动应用开发领域,相机功能一直是用户最关注的核心体验之一。作为一名Android开发者,掌握Camera API的开发技能不仅能让你实现基础的拍照录像功能,更能创造出AR滤镜、文档扫描、人脸识别等创新应用。Android系统从早期的Camera API到现在的CameraX,经历了多次架构革新,每次升级都带来了更好的性能和更简洁的API设计。
目前主流的Android相机开发方案主要有三种:传统的Camera2 API、Jetpack组件CameraX,以及一些第三方SDK(如Google的ML Kit)。Camera2 API提供了最底层的控制能力,适合需要精细调节相机参数的场景;CameraX则大幅简化了开发流程,内置了生命周期管理和设备兼容性处理;而第三方SDK通常针对特定功能(如二维码扫描)做了高度封装。
提示:从Android 5.0(API 21)开始,Google逐步废弃了旧的Camera API,全面转向Camera2 API。新项目应优先考虑Camera2或CameraX。
2. 开发环境准备与基础配置
2.1 Android Studio环境搭建
Camera开发需要完整的Android开发环境。首先确保已安装最新版Android Studio(当前稳定版为2023.3.1),安装时勾选"Android SDK"、"Android SDK Platform"和"Intel x86 Emulator Accelerator (HAXM installer)"。对于国内开发者,建议在SDK Manager中配置镜像源(如阿里云镜像)加速依赖下载。
在项目的build.gradle中需要添加CameraX依赖:
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:${camerax_version}"
}
2.2 权限与特性声明
Android相机应用必须在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" />
注意:从Android 6.0开始,相机权限属于危险权限,需要运行时动态申请。完整的权限请求代码应包括权限检查、请求和回调处理。
2.3 设备兼容性检查
在调用相机API前,应检查设备是否支持所需功能:
kotlin复制val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameraIds = cameraManager.cameraIdList
for (id in cameraIds) {
val characteristics = cameraManager.getCameraCharacteristics(id)
val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
if (capabilities?.contains(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) == true) {
// 该摄像头支持常规拍照功能
}
}
3. Camera2 API核心开发流程
3.1 相机开启与预览
Camera2 API采用管道(Pipeline)模式管理相机操作,主要涉及以下几个关键类:
- CameraManager:系统服务,用于发现和连接相机设备
- CameraDevice:代表物理相机设备
- CameraCaptureSession:管理相机捕获请求的会话
- CaptureRequest:定义捕获参数和目标的请求
典型相机预览实现代码如下:
kotlin复制// 打开相机
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
this@CameraActivity.cameraDevice = camera
// 创建预览请求
val previewRequest = camera.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW
).apply {
addTarget(surfaceView.holder.surface)
}
// 创建捕获会话
camera.createCaptureSession(
listOf(surfaceView.holder.surface),
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
this@CameraActivity.session = session
session.setRepeatingRequest(
previewRequest.build(),
null, null
)
}
}, null
)
}
}, null)
3.2 拍照功能实现
拍照流程比预览更复杂,需要处理图像保存和临时Surface:
kotlin复制// 创建临时文件保存照片
val photoFile = File(
getExternalFilesDir(Environment.DIRECTORY_PICTURES),
"IMG_${System.currentTimeMillis()}.jpg"
)
// 创建ImageReader接收照片数据
val imageReader = ImageReader.newInstance(
previewSize.width, previewSize.height,
ImageFormat.JPEG, 2
).apply {
setOnImageAvailableListener({ reader ->
val image = reader.acquireLatestImage()
// 将图像数据保存到文件
val buffer = image.planes[0].buffer
val bytes = ByteArray(buffer.remaining())
buffer.get(bytes)
FileOutputStream(photoFile).use { it.write(bytes) }
image.close()
}, handler)
}
// 创建拍照请求
val captureRequest = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE
).apply {
addTarget(imageReader.surface)
set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
set(CaptureRequest.JPEG_ORIENTATION,
getJpegOrientation(cameraCharacteristics, display.rotation))
}
// 执行拍照
session.capture(captureRequest.build(), null, null)
3.3 视频录制实现
视频录制需要配合MediaRecorder使用,典型流程包括:
- 配置MediaRecorder参数(分辨率、码率、格式等)
- 创建专门的录制Surface
- 在录制前后切换相机会话
关键代码片段:
kotlin复制// 准备MediaRecorder
val mediaRecorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(videoFile.absolutePath)
setVideoEncodingBitRate(8_000_000)
setVideoFrameRate(30)
setVideoSize(videoSize.width, videoSize.height)
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
prepare()
}
// 创建录制会话
cameraDevice.createCaptureSession(
listOf(previewSurface, mediaRecorder.surface),
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
val recordingRequest = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_RECORD
).apply {
addTarget(previewSurface)
addTarget(mediaRecorder.surface)
}
session.setRepeatingRequest(
recordingRequest.build(), null, null
)
mediaRecorder.start()
}
}, null
)
4. CameraX开发实践
4.1 CameraX架构优势
CameraX是Jetpack组件库的一部分,主要解决了传统Camera API的三大痛点:
- 设备兼容性问题:自动处理不同厂商设备的差异
- 生命周期管理:与Activity/Fragment生命周期自动绑定
- 开发复杂度:提供更简洁的API和预置用例(UseCase)
CameraX的核心概念包括:
- UseCase:定义相机用途(预览、拍照、分析)
- CameraSelector:选择前后摄像头
- ProcessCameraProvider:管理相机生命周期
- PreviewView:优化显示的预览视图
4.2 快速实现相机预览
使用CameraX实现基础预览的完整代码:
kotlin复制// 创建预览用例
val preview = Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.build()
.also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
// 获取相机提供者
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
// 绑定生命周期和用例
val cameraProvider = cameraProviderFuture.get()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, cameraSelector, preview
)
} catch(exc: Exception) {
Log.e(TAG, "相机绑定失败", exc)
}
}, ContextCompat.getMainExecutor(this))
4.3 拍照与图像分析
CameraX的拍照功能通过ImageCapture用例实现:
kotlin复制// 创建拍照用例
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// 添加到相机绑定
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture
)
// 执行拍照
imageCapture.takePicture(
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
// 处理捕获的图像
}
}
)
对于实时图像处理(如二维码扫描、人脸检测),可以使用ImageAnalysis用例:
kotlin复制val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(cameraExecutor) { image ->
// 在此处实现图像分析逻辑
image.close()
}
}
5. 高级功能与性能优化
5.1 手动控制相机参数
对于专业相机应用,可能需要手动调节以下参数:
- 曝光补偿
- 对焦模式(AF/AE锁定)
- 白平衡
- ISO感光度
- 快门速度
Camera2 API中手动控制的示例:
kotlin复制// 创建手动控制请求
val request = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_MANUAL
).apply {
// 设置手动曝光
set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_OFF)
set(CaptureRequest.SENSOR_EXPOSURE_TIME,
10_000_000L) // 10ms快门
set(CaptureRequest.SENSOR_SENSITIVITY, 800) // ISO 800
// 设置手动对焦
set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_OFF)
set(CaptureRequest.LENS_FOCUS_DISTANCE, 0.5f) // 0.5m对焦距离
}
5.2 多摄像头协同工作
现代Android设备通常配备多个摄像头,开发者可以:
- 通过CameraCharacteristics获取摄像头物理信息
- 实现摄像头切换逻辑
- 使用同步API协调多摄像头工作
获取摄像头物理位置信息的代码:
kotlin复制val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
when (lensFacing) {
CameraCharacteristics.LENS_FACING_FRONT -> "前置摄像头"
CameraCharacteristics.LENS_FACING_BACK -> "后置摄像头"
CameraCharacteristics.LENS_FACING_EXTERNAL -> "外接摄像头"
}
5.3 性能优化技巧
-
预览流畅度优化:
- 选择合适的预览分辨率(通常1080p足够)
- 使用SurfaceView代替TextureView(减少1-2帧延迟)
- 关闭不必要的图像稳定功能
-
拍照速度优化:
- 预置对焦区域(CONTROL_AF_REGIONS)
- 使用连拍模式(TEMPLATE_ZERO_SHUTTER_LAG)
- 启用硬件级JPEG编码
-
内存优化:
- 及时释放ImageReader和MediaRecorder资源
- 限制并发图像处理数量
- 使用YUV_420_888格式代替JPEG进行实时处理
-
功耗优化:
- 在不使用时关闭摄像头
- 降低预览帧率(30fps→15fps)
- 使用WAIT_FOR_QUALITY模式代替低延迟模式
6. 常见问题排查
6.1 相机启动失败问题
现象:调用openCamera()时抛出CameraAccessException
排查步骤:
- 检查是否声明了相机权限
- 确认其他应用没有独占相机资源
- 验证相机ID是否正确
- 检查设备是否支持所需功能
典型错误码:
- CAMERA_ERROR (2):相机设备遇到严重错误
- MAX_CAMERAS_IN_USE (1):相机资源被占用
- CAMERA_DISABLED (3):设备策略禁用了相机
- CAMERA_DISCONNECTED (4):相机设备断开连接
6.2 预览图像变形问题
原因分析:
- 预览宽高比与SurfaceView不匹配
- 设备屏幕方向与相机传感器方向不一致
- 选择的预览分辨率不被设备支持
解决方案:
kotlin复制// 获取支持的预览尺寸
val streamMap = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
)
val previewSizes = streamMap.getOutputSizes(SurfaceTexture::class.java)
// 选择最佳预览尺寸
fun getOptimalPreviewSize(sizes: Array<Size>, viewWidth: Int, viewHeight: Int): Size {
val aspectTolerance = 0.1
val targetRatio = viewWidth.toDouble() / viewHeight
return sizes.filter {
val ratio = it.width.toDouble() / it.height
abs(ratio - targetRatio) <= aspectTolerance
}.minBy {
abs(it.width * it.height - viewWidth * viewHeight)
} ?: sizes[0]
}
6.3 拍照延迟问题
优化方案:
- 预加载相机参数:
kotlin复制// 在开启相机前预先加载参数
val parameters = camera.getParameters()
parameters.supportedFocusModes
parameters.supportedPictureSizes
- 使用连拍模式:
kotlin复制val captureRequest = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
).apply {
addTarget(imageReader.surface)
set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
}
- 禁用不必要的处理:
kotlin复制captureRequest.set(
CaptureRequest.NOISE_REDUCTION_MODE,
CaptureRequest.NOISE_REDUCTION_MODE_OFF
)
captureRequest.set(
CaptureRequest.EDGE_MODE,
CaptureRequest.EDGE_MODE_OFF
)
7. 实战案例:构建一个简易相机应用
7.1 项目结构设计
完整相机应用应包含以下模块:
- CameraManager:封装相机核心逻辑
- UI层:预览视图、控制按钮
- 媒体存储:照片/视频保存与管理
- 设置模块:分辨率、质量等参数配置
推荐采用MVVM架构:
code复制app/
├── model/
│ ├── CameraSettings.kt
│ └── MediaFile.kt
├── view/
│ ├── CameraActivity.kt
│ └── GalleryFragment.kt
├── viewmodel/
│ └── CameraViewModel.kt
└── utils/
├── CameraHelper.kt
└── FileUtils.kt
7.2 核心功能实现
相机控制逻辑:
kotlin复制class CameraHelper(
private val context: Context,
private val lifecycleOwner: LifecycleOwner
) {
private lateinit var cameraProvider: ProcessCameraProvider
private var imageCapture: ImageCapture? = null
fun startCamera(previewView: PreviewView) {
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
imageCapture = ImageCapture.Builder()
.setBufferFormat(ImageFormat.JPEG)
.build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
imageCapture
)
} catch(exc: Exception) {
Log.e(TAG, "相机绑定失败", exc)
}
}, ContextCompat.getMainExecutor(context))
}
fun takePicture(callback: (File) -> Unit) {
val imageCapture = imageCapture ?: return
val photoFile = File(
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES),
"IMG_${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) {
callback(photoFile)
}
}
)
}
}
7.3 功能扩展建议
- AR滤镜:通过RenderScript或OpenGL ES实时处理预览帧
- 文档扫描:集成OpenCV实现边缘检测和透视变换
- 夜景模式:使用多帧合成技术提升低光画质
- 专业模式:添加手动控制面板(ISO、快门速度等)
- AI场景识别:集成ML Kit实现自动场景优化
实现基础滤镜的示例:
kotlin复制imageAnalysis.setAnalyzer(cameraExecutor) { image ->
// 转换为Bitmap
val bitmap = image.toBitmap()
// 应用滤镜
val filteredBitmap = applySepiaFilter(bitmap)
// 显示处理结果
runOnUiThread {
filterPreview.setImageBitmap(filteredBitmap)
}
image.close()
}
private fun applySepiaFilter(bitmap: Bitmap): Bitmap {
val result = bitmap.copy(bitmap.config, true)
val width = result.width
val height = result.height
val pixels = IntArray(width * height)
result.getPixels(pixels, 0, width, 0, 0, width, height)
for (i in pixels.indices) {
val r = Color.red(pixels[i])
val g = Color.green(pixels[i])
val b = Color.blue(pixels[i])
val newR = min(255, (r * 0.393 + g * 0.769 + b * 0.189).toInt())
val newG = min(255, (r * 0.349 + g * 0.686 + b * 0.168).toInt())
val newB = min(255, (r * 0.272 + g * 0.534 + b * 0.131).toInt())
pixels[i] = Color.rgb(newR, newG, newB)
}
result.setPixels(pixels, 0, width, 0, 0, width, height)
return result
}
在开发过程中,我发现CameraX的自动旋转处理有时会导致预览方向不正确。解决方法是显式设置目标旋转:
kotlin复制Preview.Builder()
.setTargetRotation(display.rotation)
.build()
ImageCapture.Builder()
.setTargetRotation(display.rotation)
.build()
另一个实用技巧是在低端设备上降低图像分析的分辨率以提高性能:
kotlin复制ImageAnalysis.Builder()
.setTargetResolution(Size(640, 480))
.build()
