首页 > 解决方案 > 从 CameraX analyze() 导航到另一个片段会阻止当前片段的生命周期并冻结 UI

问题描述

我所有的 CameraX 初始化都驻留在FragmentA其中,我的目标是FragmentB根据必须在内部验证的某些条件进行导航analyze()

直接从 导航时analyze(),通过 Logcat 我可以看到它FragmentB已正确加载,但 UI 在相机预览时冻结,并且仅在我导航回FragmentA. 我发现在这些情况下FragmentA并没有正确地完成其生命周期的其余部分,这意味着onDestroyView()只有在我导航回它时才调用其他方法,然后立即开始新的生命周期;这导致在cameraExecutor.shutdown()需要时不被调用。


编辑:我更新了代码以反映我最近寻找解决方案的尝试。我添加了一个适当的回调,它至少看起来比我以前做的更好,但它仍然没有帮助。

出于好奇,我Button在 CameraX 的布局中添加了一个旁边PreviewViewFragmentA以便它调用findNavController().navigate(). 瞧,直接单击它会使一切按预期工作,但不幸的是,我必须在没有任何用户输入的情况下以编程方式进行。如果我通过调用Button#callOnClick()Button#performClick()从回调中模拟按钮单击,它将不再起作用。

class MyAnalyzer(private val callback: () -> Unit) : ImageAnalysis.Analyzer {
    override fun analyze(imageProxy: ImageProxy) {
        if (foo) {
            imageProxy.close()
            callback()
        }

        // do other stuff with imageProxy...

        imageProxy.close()
    }
}

class FragmentA : Fragment() {
    // rest of the code...

    private lateinit var cameraExecutor: ExecutorService

    override fun onDestroyView() {
        super.onDestroyView()

        Log.d(TAG, "executor shutdown")
        cameraExecutor.shutdown()

        Log.d(TAG, "FragmentA destroyed")
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        cameraExecutor = Executors.newSingleThreadExecutor()

        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())

        cameraProviderFuture.addListener(Runnable {
            // other CameraX code...
            
            val imageAnalysis = ImageAnalysis.Builder()
                .setTargetRotation(rotation)
                .build()
                .also {
                    it.setAnalyzer(
                        cameraExecutor, MyAnalyzer({
                            findNavController().navigate(R.id.action_fragmentA_to_fragmentB)
                        })
                    )
                }

            // bind to lifecycle of use cases
        }, ContextCompat.getMainExecutor(requireContext()))
    }

标签: androidkotlinandroid-camerax

解决方案


原来答案从一开始就在我的眼皮底下,但直到几天前我才看到它。起初我完全相信相机有问题,因为问题似乎源于应用程序的一部分。

然后我意识到,当从FragmentAto导航时FragmentB,后者在第一个完成之前就开始了它的生命周期;我开始怀疑这是否真的是片段或导航组件的错,因为如果是这种情况,冻结可能是由于在片段交换期间相机没有足够快地释放引起的。经过一些一般测试后,我发现显然这是所有片段(甚至可能是活动)的工作方式,所以我放弃了这种可能性。

经过一番修改后,我随机发现 ifFragmentB代码完全空白(在 Kotlin 文件中)它工作得很好,但我真的不明白为什么会这样。又过了一段时间,它终于击中了我:我一直没有意识到这是我的代码内部的错,FragmentB因为我正在运行一个看起来很无辜的循环,持续了很长时间,显然它是在主线程上执行的冻结用户界面。

知道你不应该在主线程上执行缓慢或昂贵的代码,但是所有的教程总是提到网络请求或文件操作,所以我从来没有考虑过即使是长循环也会有同样的副作用。当然,在将循环包装在协程中之后,谜团就解决了。


推荐阅读