相机与拍照
本教程共 100 篇 · 第 89 篇 · 更新于 2026-07-28 · 约 8 分钟阅读
89. 相机与拍照
本节目标:学会用 CameraX 实现相机预览和拍照,掌握权限申请、Preview、ImageCapture、ImageAnalysis、前后摄切换,知道什么时候用 Intent 拍照就够。
相机是手机最常用的硬件之一。Android 提供两套 API:老的 Camera(已废弃)和 Camera2(功能强但难用)。CameraX 是 Jetpack 封装,简化了用法,本教程主线。
CameraX 是什么
CameraX 是 Jetpack 的相机库,在 Camera2 之上封装一层友好 API。特点:
- 生命周期感知:自动绑定到 Activity/Fragment 生命周期。
- 设备兼容:处理了各种设备差异,不用自己写兼容代码。
- 易用:预览、拍照、图像分析三行代码就能跑。
- 一致性:API 在不同版本表现一致。
两种拍照方式
1. Intent 拍照(简单)
让系统相机应用帮你拍,应用拿结果。适合「拍张照片上传」这种简单场景,不用自己写 UI。
val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
// 用 imageUri 显示或上传
}
}
val imageUri = createImageUri() // 自己创建一个 URI 给系统存
takePicture.launch(imageUri)
ActivityResultContracts.TakePicture() 是官方封装,自动调起系统相机。
需要先创建一个 URI 给相机存图:
private fun createImageUri(): Uri? {
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "photo_${System.currentTimeMillis()}.jpg")
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
}
return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
}
Android 10+ 用 MediaStore 创建 URI,不用申请存储权限。
2. CameraX 自定义(灵活)
自己写预览界面、控制拍照参数。适合「美颜相机」「扫描应用」这种需要自定义 UI 或者图像分析的场景。
下面主要讲 CameraX。
加依赖
dependencies {
val cameraxVersion = "1.4.1"
implementation("androidx.camera:camera-core:$cameraxVersion")
implementation("androidx.camera:camera-camera2:$cameraxVersion")
implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
implementation("androidx.camera:camera-view:$cameraxVersion")
}
权限:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
CAMERA 是运行时权限,要请求。
请求权限
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) startCamera()
}
LaunchedEffect(Unit) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
permissionLauncher.launch(Manifest.permission.CAMERA)
}
}
预览(Preview)
Compose 里用 PreviewView 显示相机画面:
@Composable
fun CameraPreviewScreen() {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val previewView = remember { PreviewView(context) }
val cameraExecutor = remember { Executors.newSingleThreadExecutor() }
AndroidView(factory = { previewView })
LaunchedEffect(Unit) {
val cameraProvider = ProcessCameraProvider.getInstance(context).await() // 挂起拿
val preview = Preview.Builder().build().apply {
setSurfaceProvider(previewView.surfaceProvider)
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview
)
} catch (e: Exception) {
Log.e("Camera", "绑定失败", e)
}
}
DisposableEffect(Unit) {
onDispose {
cameraExecutor.shutdown()
}
}
}
要点:
ProcessCameraProvider.getInstance(context).await()用await()挂起拿(需要camera-lifecycle的 Guava 扩展,或者用ListenableFuture转协程)。Preview+setSurfaceProvider显示预览。CameraSelector.DEFAULT_BACK_CAMERA后摄,DEFAULT_FRONT_CAMERA前摄。bindToLifecycle绑定生命周期,自动启停。
拍照(ImageCapture)
val imageCapture = remember {
ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // 低延迟
.build()
}
// 绑定时加上 imageCapture
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
imageCapture // 加进去
)
// 拍照
fun takePhoto(context: Context) {
val file = File(context.cacheDir, "photo_${System.currentTimeMillis()}.jpg")
val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(context),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = output.savedUri ?: Uri.fromFile(file)
// 显示或上传
}
override fun onError(exception: ImageCaptureException) {
Log.e("Camera", "拍照失败", exception)
}
}
)
}
拍照保存到文件,回调里拿到 URI。
图像分析(ImageAnalysis)
实时分析相机画面,做 QR 扫码、人脸检测这种:
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
// 分析 imageProxy
val bitmap = imageProxy.toBitmap()
// 检测二维码、人脸等...
imageProxy.close() // 必须关闭,否则后续帧不来
}
// 绑定
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
imageCapture,
imageAnalysis
)
Warning每一帧分析完必须
imageProxy.close(),否则下一帧不进来,相机卡死。
切换前后摄
var lensFacing by remember { mutableStateOf(CameraSelector.LENS_FACING_BACK) }
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(lensFacing)
.build()
// 切换按钮
Button(onClick = {
lensFacing = if (lensFacing == CameraSelector.LENS_FACING_BACK) {
CameraSelector.LENS_FACING_FRONT
} else {
CameraSelector.LENS_FACING_BACK
}
// 重新绑定
}) {
Text("切换")
}
切换镜头就是重新 bindToLifecycle。
闪光灯
// 拿到 Camera 对象
val camera = cameraProvider.bindToLifecycle(...)
// 设置闪光模式
imageCapture.flashMode = ImageCapture.FLASH_MODE_AUTO // AUTO / ON / OFF
// 或者开启手电筒(持续亮)
camera.cameraControl.enableTorch(true)
视频录制
VideoCapture 用法类似 ImageCapture:
val videoCapture = with(VideoCapture.Builder()
.setVideoQualityProfile(QualityProfile.UHD)
.build()) {
// 配置 Recorder
}
// 开始录制
videoCapture.startRecording(
OutputFileOptions.Builder(file).build(),
ContextCompat.getMainExecutor(context),
object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved(output: VideoCapture.OutputFileResults) {}
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {}
}
)
// 停止
videoCapture.stopRecording()
Note视频录制需要
RECORD_AUDIO权限。CameraX 1.3+ 用RecorderAPI,更稳定。
Intent 拍照对比
| 场景 | 选什么 |
|---|---|
| 拍张照片上传 | Intent TakePicture |
| 选已有照片 | PickVisualMedia |
| 自定义预览 UI | CameraX |
| 实时图像分析 | CameraX ImageAnalysis |
| 视频录制 | CameraX VideoCapture 或者 Intent |
简单需求用 Intent,复杂需求用 CameraX。
常见坑
- 权限没申请就启动相机:黑屏或者崩。先请求权限。
bindToLifecycle不在主线程:崩。ProcessCameraProvider.getInstance返回ListenableFuture,回调在主线程,别切线程。imageProxy忘记 close:相机卡死。- cameraExecutor 没 shutdown:线程泄漏。
- Android 11+ PreviewView 没设
ImplementationMode:默认兼容模式性能差,设IMPLEMENTATION_MODE_PERFORMANCE。 - 旋转屏绑定时 unbindAll 时机错:先 unbind 再 bind,避免重复绑定报错。
小结
简单拍照用 ActivityResultContracts.TakePicture(),复杂场景用 CameraX。CameraX 三大用例:Preview 预览、ImageCapture 拍照、ImageAnalysis 分析,都通过 bindToLifecycle 绑定。Compose 里用 AndroidView 包 PreviewView。前后摄切换、闪光灯、视频录制都有现成 API。
下一章讲图片加载,用 Coil 加载网络图片。