CameraX 在 ImageCapture 后停止片段时抛出异常

CameraX Throws Exceptions When Stopping Fragment After ImageCapture

高级概述:我正在尝试拍摄照片,并在成功拍摄图像后 return 返回到上一个屏幕。

由于某些原因,在返回时抛出了几个与 CameraX 相关的异常。

2020-12-26 19:00:43.740 8218-8218/com.webslinger.dejavu E/TakePictureUseCase$execute: Photo capture failed: Camera is closed.
    androidx.camera.core.ImageCaptureException: Camera is closed.
        at androidx.camera.core.ImageCapture$ImageCaptureRequest.lambda$notifyCallbackError$ImageCapture$ImageCaptureRequest(ImageCapture.java:2232)
        at androidx.camera.core.-$$Lambda$ImageCapture$ImageCaptureRequest$KlqAxzwB-08wcOFrjThjf8ncF2g.run(Unknown Source:8)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:205)
        at android.app.ActivityThread.main(ActivityThread.java:6991)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:884)
     Caused by: androidx.camera.core.CameraClosedException: Camera is closed.
        at androidx.camera.core.ImageCapture.abortImageCaptureRequests(ImageCapture.java:809)
        at androidx.camera.core.ImageCapture.onStateDetached(ImageCapture.java:805)
        at androidx.camera.camera2.internal.Camera2CameraImpl.notifyStateDetachedToUseCases(Camera2CameraImpl.java:732)
        at androidx.camera.camera2.internal.Camera2CameraImpl.detachUseCases(Camera2CameraImpl.java:767)
        at androidx.camera.core.internal.CameraUseCaseAdapter.detachUseCases(CameraUseCaseAdapter.java:280)
        at androidx.camera.lifecycle.LifecycleCamera.onStop(LifecycleCamera.java:93)
        at androidx.camera.lifecycle.LifecycleCamera.suspend(LifecycleCamera.java:119)
        at androidx.camera.lifecycle.LifecycleCameraRepository.suspendUseCases(LifecycleCameraRepository.java:433)
        at androidx.camera.lifecycle.LifecycleCameraRepository.setInactive(LifecycleCameraRepository.java:387)
        at androidx.camera.lifecycle.LifecycleCameraRepository$LifecycleCameraRepositoryObserver.onStop(LifecycleCameraRepository.java:504)
        at java.lang.reflect.Method.invoke(Native Method)
        at androidx.lifecycle.ClassesInfoCache$MethodReference.invokeCallback(ClassesInfoCache.java:219)
        at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeMethodsForEvent(ClassesInfoCache.java:194)
        at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeCallbacks(ClassesInfoCache.java:185)
        at androidx.lifecycle.ReflectiveGenericLifecycleObserver.onStateChanged(ReflectiveGenericLifecycleObserver.java:37)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
        at androidx.lifecycle.LifecycleRegistry.backwardPass(LifecycleRegistry.java:284)
        at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:302)
        at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:148)
        at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134)
        at androidx.fragment.app.FragmentViewLifecycleOwner.handleLifecycleEvent(FragmentViewLifecycleOwner.java:62)
        at androidx.fragment.app.Fragment.performStop(Fragment.java:3166)
        at androidx.fragment.app.FragmentStateManager.stop(FragmentStateManager.java:630)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:324)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2168)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2094)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1990)
        at androidx.fragment.app.FragmentManager.run(FragmentManager.java:524)
        at android.os.Handler.handleCallback(Handler.java:873) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:205) 
        at android.app.ActivityThread.main(ActivityThread.java:6991) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:884) 
2020-12-26 19:00:43.745 8218-8218/com.webslinger.dejavu E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.webslinger.dejavu, PID: 8218
    java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:503)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:884)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:884) 
     Caused by: androidx.camera.core.ImageCaptureException: Camera is closed.
        at androidx.camera.core.ImageCapture$ImageCaptureRequest.lambda$notifyCallbackError$ImageCapture$ImageCaptureRequest(ImageCapture.java:2232)
        at androidx.camera.core.-$$Lambda$ImageCapture$ImageCaptureRequest$KlqAxzwB-08wcOFrjThjf8ncF2g.run(Unknown Source:8)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:205)
        at android.app.ActivityThread.main(ActivityThread.java:6991)
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:884) 
     Caused by: androidx.camera.core.CameraClosedException: Camera is closed.
        at androidx.camera.core.ImageCapture.abortImageCaptureRequests(ImageCapture.java:809)
        at androidx.camera.core.ImageCapture.onStateDetached(ImageCapture.java:805)
        at androidx.camera.camera2.internal.Camera2CameraImpl.notifyStateDetachedToUseCases(Camera2CameraImpl.java:732)
        at androidx.camera.camera2.internal.Camera2CameraImpl.detachUseCases(Camera2CameraImpl.java:767)
        at androidx.camera.core.internal.CameraUseCaseAdapter.detachUseCases(CameraUseCaseAdapter.java:280)
        at androidx.camera.lifecycle.LifecycleCamera.onStop(LifecycleCamera.java:93)
        at androidx.camera.lifecycle.LifecycleCamera.suspend(LifecycleCamera.java:119)
        at androidx.camera.lifecycle.LifecycleCameraRepository.suspendUseCases(LifecycleCameraRepository.java:433)
        at androidx.camera.lifecycle.LifecycleCameraRepository.setInactive(LifecycleCameraRepository.java:387)
        at androidx.camera.lifecycle.LifecycleCameraRepository$LifecycleCameraRepositoryObserver.onStop(LifecycleCameraRepository.java:504)
        at java.lang.reflect.Method.invoke(Native Method)
        at androidx.lifecycle.ClassesInfoCache$MethodReference.invokeCallback(ClassesInfoCache.java:219)
        at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeMethodsForEvent(ClassesInfoCache.java:194)
        at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeCallbacks(ClassesInfoCache.java:185)
        at androidx.lifecycle.ReflectiveGenericLifecycleObserver.onStateChanged(ReflectiveGenericLifecycleObserver.java:37)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
        at androidx.lifecycle.LifecycleRegistry.backwardPass(LifecycleRegistry.java:284)
        at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:302)
        at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:148)
        at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134)
        at androidx.fragment.app.FragmentViewLifecycleOwner.handleLifecycleEvent(FragmentViewLifecycleOwner.java:62)
        at androidx.fragment.app.Fragment.performStop(Fragment.java:3166)
        at androidx.fragment.app.FragmentStateManager.stop(FragmentStateManager.java:630)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:324)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2168)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2094)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1990)
        at androidx.fragment.app.FragmentManager.run(FragmentManager.java:524)

我已经测试了仅在启动预览用例后向后导航并且没有问题,所以我认为问题与捕获用例有关。

我也曾尝试在退出片段之前手动取消绑定用例,尽管这应该是不必要的,因为相机用例已绑定到片段的生命周期。

我是这个库的新手,如有任何想法,我们将不胜感激。

class TakeAfterPictureFragment : BaseFragment() {
private lateinit var dataBinding: TakeAfterPictureFragmentBinding
private lateinit var viewModel: TakeAfterPictureViewModel

@Inject
lateinit var viewModelFactory: TakeAfterPictureViewModelFactory

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View{
    dataBinding = DataBindingUtil.inflate(
        inflater,
        R.layout.take_after_picture_fragment,
        container,
        false
    )

    return dataBinding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    inject(this)

    viewModel = ViewModelProvider(
        this,
        viewModelFactory
    ).get(TakeAfterPictureViewModel::class.java)

    loadBeforePicture()
    checkCameraPermissions()
    bindCameraCaptureButton()
    bindOnPhotoCaptured()
}

private fun loadBeforePicture() {
    arguments?.let {
        val beforePictureUri: Uri = it.get("BEFORE_PICTURE_PATH") as Uri

        Glide.with(this)
            .load(beforePictureUri)
            .into(dataBinding.beforePictureOverlay)
    }

    dataBinding.beforePictureOverlay.imageAlpha = 70
}

private fun checkCameraPermissions() {
    if (allPermissionsGranted()) {
        viewModel.startCameraPreview(viewLifecycleOwner, dataBinding.viewFinder.surfaceProvider)
    } else {
        ActivityCompat.requestPermissions(
            requireActivity(), REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
        )
    }
}

private fun bindCameraCaptureButton() {
    dataBinding.cameraCaptureButton.setOnClickListener {
        viewModel.takeAfterPicture()
    }
}

private fun bindOnPhotoCaptured(){
    viewModel.photoUri.observe(viewLifecycleOwner, Observer {
        Toast.makeText(requireContext(), "Photo capture successful.", Toast.LENGTH_LONG).show()
        viewModel.navigateBack(screenNavigator)
    })
}

override fun onRequestPermissionsResult(
    requestCode: Int, permissions: Array<String>, grantResults:
    IntArray
) {
    if (requestCode == REQUEST_CODE_PERMISSIONS) {
        if (allPermissionsGranted()) {
            viewModel.startCameraPreview(viewLifecycleOwner, dataBinding.viewFinder.surfaceProvider)
        } else {
            showPermissionsNotGrantedMessage()
            viewModel.navigateBack(screenNavigator)
        }
    }
}

private fun showPermissionsNotGrantedMessage() {
    Toast.makeText(
        requireContext(),
        "Permissions not granted by the user.",
        Toast.LENGTH_SHORT
    ).show()
}

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

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

companion object {
    fun newInstance() = TakeAfterPictureFragment()
    private const val REQUEST_CODE_PERMISSIONS = 10
    private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}

}

    class TakeAfterPictureViewModel(
    private val takePictureUseCase: TakePictureUseCase,
    private val outputDirectoryProvider: PhotoOutputDirectoryProvider,
    private val camera: ICamera,
    private val executor: Executor
    ) : ViewModel() {

    private val _photoUri: MutableLiveData<Uri> = MutableLiveData()
    val photoUri: LiveData<Uri> = _photoUri

    fun startCameraPreview(lifecycleOwner: LifecycleOwner, surfaceProvider: Preview.SurfaceProvider){
        camera.start(
            executor,
            lifecycleOwner,
            surfaceProvider
        )
    }

    fun takeAfterPicture(){
        val photoFile = File(
            outputDirectoryProvider.getOutputDirectory(),
            SimpleDateFormat(
                FILENAME_FORMAT, Locale.US
            ).format(System.currentTimeMillis()) + ".jpg"
        )

        _photoUri.value = takePictureUseCase.execute(
            camera,
            photoFile,
        )
    }

    fun navigateBack(screenNavigator: ScreenNavigator){
        screenNavigator.navigateBack()
    }

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

}

class DefaultCamera(
    private val cameraProviderFuture: ListenableFuture<ProcessCameraProvider>,
    private val previewConfiguration: IPreviewConfiguration,
    private val imageCaptureConfiguration: IImageCaptureConfiguration,
) : ICamera {
    private val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
    private var imageCapture: ImageCapture = imageCaptureConfiguration.configure()
    private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    override fun start(
        executor: Executor,
        lifeCycleOwner: LifecycleOwner,
        previewSurface: Preview.SurfaceProvider
    ) {
        cameraProviderFuture.addListener(
            Runnable {
                try {
                    bindPreview(
                        lifeCycleOwner,
                        previewConfiguration.configure(previewSurface)
                    )
                } catch (exc: Exception) {
                    Timber.e(exc, "Use case binding failed")
                }
            }, executor
        )
    }

    private fun bindPreview(lifeCycleOwner: LifecycleOwner, preview: Preview) {
        // Unbind use cases before rebinding
        cameraProvider.unbindAll()

        // Bind use cases to camera
        cameraProvider.bindToLifecycle(
            lifeCycleOwner,
            cameraSelector,
            preview,
            imageCapture
        )
    }

    override fun takePhoto(
        outputOptions: ImageCapture.OutputFileOptions,
        executor: Executor,
        onImageSavedCallback: ImageCapture.OnImageSavedCallback
    ) {
        val imageCapture = imageCapture ?: return

        imageCapture.takePicture(
            outputOptions,
            executor,
            onImageSavedCallback
        )

    }

    override fun stop() {
        cameraProvider.unbindAll()
    }
}

堆栈跟踪表明图像捕获在完成之前被中止,这意味着您在接收到图像捕获结果之前正在返回。从代码来看,似乎 takeAfterPicture() 调用 takePictureUseCase.execute() 立即更新 _photoUri 的值,大概这会触发对 navigateBack() 的调用,并抛出异常你'重新观察。

DefaultCameratakePhoto()方法内部调用了ImageCapture.takePicture()并传递了一个OnImageSavedCallback的实例,_photoUri的值应该只在调用回调的成功方法。