首页 / Android 入门教程 / 相机与拍照

Android 入门教程

相机与拍照

本教程共 100 篇 · 第 89 篇 · 更新于 2026-07-28 · 约 8 分钟阅读

AndroidAndroid 入门教程CameraX相机拍照图像分析

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+ 用 Recorder API,更稳定。

Intent 拍照对比

场景选什么
拍张照片上传Intent TakePicture
选已有照片PickVisualMedia
自定义预览 UICameraX
实时图像分析CameraX ImageAnalysis
视频录制CameraX VideoCapture 或者 Intent

简单需求用 Intent,复杂需求用 CameraX。

常见坑

  1. 权限没申请就启动相机:黑屏或者崩。先请求权限。
  2. bindToLifecycle 不在主线程:崩。ProcessCameraProvider.getInstance 返回 ListenableFuture,回调在主线程,别切线程。
  3. imageProxy 忘记 close:相机卡死。
  4. cameraExecutor 没 shutdown:线程泄漏。
  5. Android 11+ PreviewView 没设 ImplementationMode:默认兼容模式性能差,设 IMPLEMENTATION_MODE_PERFORMANCE
  6. 旋转屏绑定时 unbindAll 时机错:先 unbind 再 bind,避免重复绑定报错。

小结

简单拍照用 ActivityResultContracts.TakePicture(),复杂场景用 CameraX。CameraX 三大用例:Preview 预览、ImageCapture 拍照、ImageAnalysis 分析,都通过 bindToLifecycle 绑定。Compose 里用 AndroidViewPreviewView。前后摄切换、闪光灯、视频录制都有现成 API。

下一章讲图片加载,用 Coil 加载网络图片。