从 CameraX analyze() 导航到另一个片段会阻止当前片段的生命周期并冻结 UI

Navigating to another fragment from CameraX analyze() blocks current fragment's lifecycle and freezes the UI

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

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


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

出于好奇,我在 FragmentA 的布局中的 CameraX PreviewView 旁边添加了一个 Button,以便它调用 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()))
    }

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

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

经过一些修改后,我随机发现如果 FragmentB 在代码方面完全空白(在 Kotlin 文件中),它工作正常,但我真的不明白为什么会这样。又过了一段时间,它终于击中了我:我一直没有意识到这是我的代码在 FragmentB 中的错误,因为我是 运行 一个看似无辜的循环,持续了很长时间,而且显然它在冻结 UI.

的主线程上执行

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