Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
242 views
in Technique[技术] by (71.8m points)

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

All my CameraX initializations reside in FragmentA, and my goal is to navigate to FragmentB depending on some condition that must be verified inside analyze().

When navigating directly from analyze(), through Logcat I can see that FragmentB is loaded correctly but the UI freezes on the camera preview, and unfreezes only when I navigate back to FragmentA. I discovered that under those circumstances FragmentA doesn't go through the rest of its lifecycle correctly, meaning that onDestroyView() and the other methods are called only when I navigate back to it, before immediately beginning a new lifecycle; this leads to cameraExecutor.shutdown() not being called when it's required.


Edit: I updated the code to reflect my latest attempts at finding a solution. I've added a proper callback which at least looks nicer than what I was doing before, but it still doesn't help.

Out of curiosity I've added a Button beside CameraX PreviewView in FragmentA's layout, so that it calls findNavController().navigate(). Lo and behold, clicking directly on it makes it all work as expected, but unfortunately I must do it programmatically without any user input. And if I simulate the button click by calling Button#callOnClick() or Button#performClick() from within the callback it doesn't work again.

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()))
    }
question from:https://stackoverflow.com/questions/65834355/navigating-to-another-fragment-from-camerax-analyze-blocks-current-fragments

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It turns out the answer was under my nose since the beginning but I couldn't see it until a few days ago. At first I was fully convinced that there was something wrong with the camera, because it was the part of the app where the issue appeared to be originating from.

Then I realized that when navigating from FragmentA to FragmentB, the latter begins its lifecycle before that of the first one is completed; I began to wonder if maybe it was actually the fragment's or the Navigation Component's fault, because if that were the case the freeze would have probably been caused by the camera not being released quickly enough during the fragment swap. After some general testing I discovered that apparently that's the way all fragments (and maybe even activities) work, so I gave up the possibility.

After some more tinkering I randomly discovered that if FragmentB was totally blank code-wise (in the Kotlin file) it worked fine, but I really couldn't understand why that was the case. After some more time it finally hit me: I had always failed to realize that it was my code's fault inside FragmentB because I was running an innocent-looking loop which lasted for a very long time, and obviously it was being executed on the main thread freezing the UI.

I knew that you shouldn't execute slow or expensive code on the main thread but all the tutorials always mention network requests or file operations, so I never once took in consideration that even a lenghty loop could have the same side effects. Of course after wrapping the loop in a coroutine the mistery was solved.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...