You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将旧版CameraX拍照代码升级至1.3.0稳定版?

CameraX 从1.0.0 alpha05 升级到1.3.0稳定版完整指南及代码示例

一、依赖配置升级

替换旧版CameraX依赖,使用1.3.0稳定版组件:

// 模块级 build.gradle
dependencies {
    // CameraX 核心生命周期管理
    implementation "androidx.camera:camera-lifecycle:1.3.0"
    // Camera2 扩展适配
    implementation "androidx.camera:camera-camera2:1.3.0"
    // 预览用例
    implementation "androidx.camera:camera-preview:1.3.0"
    // 拍照用例
    implementation "androidx.camera:camera-core:1.3.0"
    // 可选:简化预览布局的View组件
    implementation "androidx.camera:camera-view:1.3.0"
}

二、核心API变更说明

  • 移除CameraX.unbindAll()/CameraX.bindToLifecycle(),改用ProcessCameraProvider统一管理相机生命周期绑定
  • 相机用例(预览、拍照)通过标准化Builder模式创建
  • 摄像头切换通过修改CameraSelector后重新绑定实现,无需手动解绑再绑定

三、完整代码示例(Kotlin)

1. Activity代码

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.io.File
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

class CameraXActivity : AppCompatActivity() {
    private lateinit var previewView: PreviewView
    private lateinit var captureBtn: Button
    private lateinit var switchCameraBtn: Button
    private var imageCapture: ImageCapture? = null
    private var cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
    private lateinit var cameraExecutor: ExecutorService

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

        previewView = findViewById(R.id.preview_view)
        captureBtn = findViewById(R.id.capture_btn)
        switchCameraBtn = findViewById(R.id.switch_camera_btn)
        cameraExecutor = Executors.newSingleThreadExecutor()

        // 权限校验
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(
                this, REQUIRED_PERMISSIONS, REQUEST_PERM_CODE
            )
        }

        // 拍照逻辑
        captureBtn.setOnClickListener { takePhoto() }

        // 摄像头切换逻辑
        switchCameraBtn.setOnClickListener {
            cameraSelector = if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
                CameraSelector.DEFAULT_FRONT_CAMERA
            } else {
                CameraSelector.DEFAULT_BACK_CAMERA
            }
            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()

            try {
                // 解绑所有已绑定的用例
                cameraProvider.unbindAll()
                // 绑定用例到Activity生命周期
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture
                )
            } catch (e: Exception) {
                Toast.makeText(this, "相机启动失败: ${e.message}", Toast.LENGTH_SHORT).show()
            }
        }, ContextCompat.getMainExecutor(this))
    }

    private fun takePhoto() {
        val imageCapture = imageCapture ?: return

        // 生成照片存储路径
        val photoFile = File(
            externalMediaDirs.firstOrNull(),
            "${System.currentTimeMillis()}.jpg"
        )

        val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

        imageCapture.takePicture(
            outputOptions,
            ContextCompat.getMainExecutor(this),
            object : ImageCapture.OnImageSavedCallback {
                override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                    Toast.makeText(this@CameraXActivity, "照片已保存: ${photoFile.path}", Toast.LENGTH_SHORT).show()
                }

                override fun onError(e: ImageCaptureException) {
                    Toast.makeText(this@CameraXActivity, "拍照失败: ${e.message}", Toast.LENGTH_SHORT).show()
                }
            }
        )
    }

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_PERM_CODE) {
            if (allPermissionsGranted()) {
                startCamera()
            } else {
                Toast.makeText(this, "权限被拒绝,无法使用相机", Toast.LENGTH_SHORT).show()
                finish()
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
    }

    companion object {
        private const val REQUEST_PERM_CODE = 100
        private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
    }
}

2. 布局文件(activity_camera.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.camera.view.PreviewView
        android:id="@+id/preview_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:gravity="center"
        android:orientation="horizontal">

        <Button
            android:id="@+id/switch_camera_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="切换摄像头" />

        <Button
            android:id="@+id/capture_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="拍照"
            android:layout_marginStart="32dp" />

    </LinearLayout>

</LinearLayout>

四、关键注意事项

  • Android 11适配:保存到外部媒体目录无需额外存储权限,若需自定义目录需申请MANAGE_EXTERNAL_STORAGE权限
  • 生命周期绑定:ProcessCameraProvider.bindToLifecycle()会自动跟随Activity生命周期,页面销毁时无需手动解绑
  • 异常处理:必须捕获相机绑定、拍照过程中的异常,避免因硬件兼容性问题(如部分Oppo设备)导致崩溃
  • 线程安全:拍照操作需在后台线程执行,示例中使用ExecutorService确保线程安全

内容的提问来源于stack exchange,提问作者anta40

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 22:05:36