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

如何等待ImageCapture初始化完成后再拍摄照片?

CameraX 等待ImageCapture初始化完成后拍照的正确实现

为什么你的while循环会导致崩溃

用while (imageCapture == null) {}会直接阻塞主线程,而CameraX的摄像头初始化回调是在主线程执行的。主线程被死循环占住后,初始化回调根本没法运行,imageCapture永远不会被赋值,最终触发应用无响应(ANR)崩溃。

你当前代码的其他问题

  1. onCreate里连续调用startCamera()、takePhoto()、startCameraBack()、takePhoto(),此时imageCapture还未初始化,takePhoto()会直接返回(因为val imageCapture = imageCapture ?: return),而且两次启动摄像头会造成资源冲突。
  2. 缺少startCameraBack()函数的实现,你需要补上后置摄像头的初始化逻辑。

解决方案1:初始化完成后自动拍照

如果需要摄像头初始化完成后立刻拍照,直接把takePhoto()放在Camera绑定完成的回调里:

class MainActivity : AppCompatActivity() {

    private var imageCapture: ImageCapture? = null
    private lateinit var viewBinding: ActivityMainBinding
    private lateinit var outputDirectory: File
    private lateinit var cameraExecutor: ExecutorService

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)
        supportActionBar?.hide()
        outputDirectory = getOutputDirectory()
        cameraExecutor = Executors.newSingleThreadExecutor()
        // 启动后置摄像头,初始化完成后自动拍照
        startCameraBack()
    }

    // 后置摄像头初始化逻辑
    private fun startCameraBack() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
            val preview = Preview.Builder().build()
            preview.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
            // 指定使用后置摄像头
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
            imageCapture = ImageCapture.Builder().build()
            // 先解绑之前的摄像头资源,避免冲突
            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
            // 初始化完成后立刻调用拍照
            takePhoto()
        }, ContextCompat.getMainExecutor(this))
    }

    private fun takePhoto() {
        val imageCapture = imageCapture ?: run {
            Toast.makeText(this, "摄像头还没准备好", Toast.LENGTH_SHORT).show()
            return
        }
        val photoFile = File(
            outputDirectory,
            SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg"
        )
        val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
        imageCapture.takePicture(
            outputOptions,
            ContextCompat.getMainExecutor(this),
            object : ImageCapture.OnImageSavedCallback {
                override fun onError(exc: ImageCaptureException) {
                    Toast.makeText(this@MainActivity, "拍照失败: ${exc.message}", Toast.LENGTH_SHORT).show()
                }
                override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                    val savedUri: Uri = Uri.fromFile(photoFile)
                    Toast.makeText(this@MainActivity,"照片已保存至 $savedUri", Toast.LENGTH_LONG).show()
                }
            })
    }

    private fun getOutputDirectory(): File {
        val mediaDir = externalMediaDirs.firstOrNull()?.let {
            File(it, resources.getString(R.string.app_name)).apply { mkdirs() }
        }
        return if (mediaDir != null && mediaDir.exists())
            mediaDir else filesDir
    }

    companion object {
        private const val TAG = "CameraXGFG"
        private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
    }

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

解决方案2:手动触发拍照(比如按钮点击)

如果需要用户点击按钮再拍照,只需要给按钮添加点击事件,takePhoto()里的判空逻辑会自动处理未初始化的情况:

在onCreate里添加按钮点击监听:

override fun onCreate(savedInstanceState: Bundle?) {
    // ... 其他初始化代码
    // 假设你的按钮id是takePhotoBtn
    viewBinding.takePhotoBtn.setOnClickListener {
        takePhoto()
    }
    // 启动后置摄像头
    startCameraBack()
}

额外注意事项

  • 不要重复初始化摄像头,每次启动前先调用cameraProvider.unbindAll()释放资源。
  • 永远不要在主线程做阻塞操作,异步初始化的逻辑必须用回调或观察者模式处理。
  • takePhoto()里的判空逻辑可以避免空指针,同时给用户友好提示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 08:50:56