如何等待ImageCapture初始化完成后再拍摄照片?
CameraX 等待ImageCapture初始化完成后拍照的正确实现
为什么你的while循环会导致崩溃
用while (imageCapture == null) {}会直接阻塞主线程,而CameraX的摄像头初始化回调是在主线程执行的。主线程被死循环占住后,初始化回调根本没法运行,imageCapture永远不会被赋值,最终触发应用无响应(ANR)崩溃。
你当前代码的其他问题
onCreate里连续调用startCamera()、takePhoto()、startCameraBack()、takePhoto(),此时imageCapture还未初始化,takePhoto()会直接返回(因为val imageCapture = imageCapture ?: return),而且两次启动摄像头会造成资源冲突。- 缺少
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
相关产品推荐
相关产品推荐

