Issue
High-Level Overview: I am attempting to capture a photo and return to the previous screen after a successful image capture.
For some reason when navigating back several CameraX related exceptions are being thrown.
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$1$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$5.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$1$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$5.run(FragmentManager.java:524)
I have tested navigating backward after only starting the preview use-case and there is no issue, so I believe the problem is related to the capture use-case specifically.
I have also tried manually unbinding the use-cases before exiting the fragment although that should be unnecessary because the camera use-cases are bound to the fragment's lifecycle.
I am new to working with this library and any ideas would be appreciated.
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()
}
}
Solution
The stacktrace indicates that the image capture is being aborted before it finishes, which means you're navigating back before receiving the image capture result. From the code, it seems that takeAfterPicture()
calls takePictureUseCase.execute()
which immediately updates _photoUri
's value, presumably this would then trigger a call to navigateBack()
, and throw the exception you're observing.
DefaultCamera
's takePhoto()
method internally calls ImageCapture.takePicture()
and passes an instance of OnImageSavedCallback
, the value of _photoUri
should only be updated after the callback's success method is invoked.
Answered By - Husayn Hakeem
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.