1. CameraX 预览与拍照最小实现

CameraX 预览与拍照最小实现 学习卡片

CameraX 预览与拍照最小实现

CameraX 通过“用例(UseCase)”把相机能力拆成可组合的模块。本例只绑定两个用例:Preview 负责把相机画面送到 PreviewView,ImageCapture 负责把一帧画面编码成 JPEG 文件。两者共享同一个 ProcessCameraProvider 和 CameraSelector,由 CameraX 统一管理相机打开、会话配置与生命周期。

关键约束:ImageCapture 的输出目录必须由应用自己提供。本例写入 getExternalFilesDir(Environment.DIRECTORY_PICTURES),即应用私有外部目录,无需存储权限,卸载应用时随应用数据一起清除。

CameraX 预览与拍照:用例绑定与数据流 LifecycleOwner Activity / Fragment onCreate → onDestroy bindToLifecycle ProcessCameraProvider CameraSelector.DEFAULT_BACK 统一管理相机会话 Preview 用例 SurfaceProvider = PreviewView 实时画面 → 屏幕显示 ImageCapture 用例 takePicture(OutputFileOptions) 单帧 → JPEG 编码 拍照按钮 点击触发 takePicture 输出文件 getExternalFilesDir(PICTURES) IMG_yyyyMMdd_HHmmss.jpg 生命周期自动管理 STARTED RESUMED PAUSED DESTROYED 相机随生命周期自动打开/释放,无需手动调用 camera.close()

最小实现代码

以下代码可直接放入一个 Activity 中运行。布局只需一个 PreviewView 和一个按钮。

// build.gradle (Module: app)
// implementation "androidx.camera:camera-core:1.3.4"
// implementation "androidx.camera:camera-camera2:1.3.4"
// implementation "androidx.camera:camera-lifecycle:1.3.4"
// implementation "androidx.camera:camera-view:1.3.4"

class CameraActivity : AppCompatActivity() {
    private lateinit var previewView: PreviewView
    private lateinit var imageCapture: ImageCapture
    private lateinit var cameraExecutor: ExecutorService

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera)

        previewView = findViewById(R.id.previewView)
        cameraExecutor = Executors.newSingleThreadExecutor()

        findViewById<Button>(R.id.btnCapture).setOnClickListener {
            takePhoto()
        }

        startCamera()
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()

            val preview = Preview.Builder().build().also {
                it.setSurfaceProvider(previewView.surfaceProvider)
            }

            imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .build()

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture
                )
            } catch (e: Exception) {
                Log.e("CameraX", "绑定失败", e)
            }
        }, ContextCompat.getMainExecutor(this))
    }

    private fun takePhoto() {
        val name = "IMG_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())}.jpg"
        val photoFile = File(
            getExternalFilesDir(Environment.DIRECTORY_PICTURES), name
        )
        val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

        imageCapture.takePicture(
            outputOptions,
            ContextCompat.getMainExecutor(this),
            object : ImageCapture.OnImageSavedCallback {
                override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                    Log.d("CameraX", "已保存: ${photoFile.absolutePath}")
                }
                override fun onError(exc: ImageCaptureException) {
                    Log.e("CameraX", "拍照失败: ${exc.message}", exc)
                }
            }
        )
    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
    }
}
验证方式:运行后预览画面应实时显示后置摄像头画面;点击按钮后 Logcat 输出 已保存: /storage/emulated/0/Android/data/<包名>/files/Pictures/IMG_xxx.jpg。用 Android Studio 的 Device File Explorer 进入该路径可看到 JPEG 文件,打开后画面与拍照瞬间的预览一致。
常见问题:若预览黑屏,检查是否在 AndroidManifest.xml 中声明了 <uses-permission android:name="android.permission.CAMERA"/> 并在运行时请求了该权限。CameraX 不会自动申请权限,缺少权限时 bindToLifecycle 会抛出异常。

关键参数与行为对照

配置项取值作用
CameraSelectorDEFAULT_BACK_CAMERA选择后置摄像头
CaptureModeMINIMIZE_LATENCY优先降低快门延迟,适合抓拍
输出目录getExternalFilesDir(PICTURES)应用私有目录,免存储权限
回调线程ContextCompat.getMainExecutor(this)在主线程更新 UI 或日志
生命周期绑定bindToLifecycle(this, ...)随 Activity 自动开关相机