简体   繁体   中英

Why are the contents of some GlobalScope.launch calls in Kotlin silently failing to execute?

I am new to Kotlin - in fact it was never my plan to use it, but I am trying to use the CameraKit library which uses what appears to be a generated Kotlin layer for its API interface. I have been having trouble with the Camera not being disconnected properly, the code in question ( or at least what seems to me the most relevant parts of it ) looks like this:

class CameraPreview : FrameLayout, CameraEvents {
    var TAG = "CameraPreview.kt"
    var lifecycleState: LifecycleState = LifecycleState.STOPPED
    var surfaceState: SurfaceState = SurfaceState.SURFACE_WAITING
    var cameraState: CameraState = CameraState.CAMERA_CLOSED
        set(state) {
            field = state
            when (state) {
                CameraState.CAMERA_OPENED -> {
                    listener?.onCameraOpened()
                }
                CameraState.PREVIEW_STARTED -> {
                    listener?.onPreviewStarted()
                }
                CameraState.PREVIEW_STOPPED -> {
                    listener?.onPreviewStopped()
                }
                CameraState.CAMERA_CLOSING -> {
                    listener?.onCameraClosed()
                }
                else -> {
                    // ignore
                }
            }
        }


    private val cameraDispatcher: CoroutineDispatcher = newSingleThreadContext("CAMERA")
    private var cameraOpenContinuation: Continuation<Unit>? = null
    private var previewStartContinuation: Continuation<Unit>? = null


    init {
        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        displayOrientation = windowManager.defaultDisplay.rotation * 90

        cameraSurfaceView.cameraSurfaceTextureListener = object : CameraSurfaceTextureListener {
            override fun onSurfaceReady(cameraSurfaceTexture: CameraSurfaceTexture) {
                surfaceTexture = cameraSurfaceTexture
                surfaceState = SurfaceState.SURFACE_AVAILABLE
                if (lifecycleState == LifecycleState.STARTED || lifecycleState == LifecycleState.RESUMED) {
                    resume()
                }
            }
        }

        addView(cameraSurfaceView)
    }

    fun start(facing: CameraFacing) {
        GlobalScope.launch(cameraDispatcher) {
            runBlocking {
                Log.i(TAG, "Start preview state is "+lifecycleState)
                lifecycleState = LifecycleState.STARTED
                cameraFacing = facing
                openCamera()
            }
        }
    }

    fun resume() {
        GlobalScope.launch(cameraDispatcher) {
            runBlocking {
                Log.i("CameraPreview.kt", "Resume preview state is "+lifecycleState)
                lifecycleState = LifecycleState.RESUMED
                try {
                    startPreview()
                } catch (e: Exception) {
                    Log.i("CameraPreview.kt", "Start preview hit an exception: "+e.message)
                    // camera or surface not ready, wait.
                }
            }
        }
    }

    fun pause() {
        Log.i("CameraPreview.kt", "Pause called");
        GlobalScope.launch(cameraDispatcher) {
            Log.i(TAG, "Pause scope launched, runblocking call ahead.")
            runBlocking {
                Log.i("CameraPreview.kt", "Pause initiated, stop preview state is "+lifecycleState)
                lifecycleState = LifecycleState.PAUSED
                stopPreview()
            }  
        }

    }

    fun stop() {
        Log.i("CameraPreview.kt", "Stop called");
        GlobalScope.launch(cameraDispatcher) {
            Log.i(TAG, "Stop scope launched, runblocking call ahead.")
            runBlocking {
                Log.i("CameraPreview.kt", "Stop initiated, close camera state is "+lifecycleState)
                lifecycleState = LifecycleState.STOPPED
                closeCamera()
            }

        }

    }



    enum class CameraState {
        CAMERA_OPENING,
        CAMERA_OPENED,
        PREVIEW_STARTING,
        PREVIEW_STARTED,
        PREVIEW_STOPPING,
        PREVIEW_STOPPED,
        CAMERA_CLOSING,
        CAMERA_CLOSED;
    }

    // Camera control:

    private suspend fun openCamera(): Unit = suspendCoroutine {
        cameraOpenContinuation = it
        cameraState = CameraState.CAMERA_OPENING
        Log.i("CameraPreview.kt", "openCamera call state is "+lifecycleState)
        cameraApi.open(cameraFacing)
    }

    private suspend fun startPreview(): Unit = suspendCoroutine {
        Log.i("CameraPreview.kt", "startPreview, lifecyclestate "+ lifecycleState);
        // do stuff

    }

    private suspend fun stopPreview(): Unit = suspendCoroutine {
        Log.i("CameraPreview.kt", "Stop preview state is "+lifecycleState)
        cameraState = CameraState.PREVIEW_STOPPING
        cameraApi.stopPreview()
        it.resume(Unit)
    }

    private suspend fun closeCamera(): Unit = suspendCoroutine {
        Log.i("CameraPreview.kt", "Close camera state is "+lifecycleState)
        cameraState = CameraState.CAMERA_CLOSING
        cameraApi.release()
        it.resume(Unit)
    }


}

Now when start and resume are called, they do what they are supposed to. When pause and stop are called, there is no perceptible change and the camera preview is neither paused nor stopped.

My logging indicates that when pause() is called, the initial call is logged, but the log statement inside the GlobalScope.launch(cameraDispatcher) never appears. It seems like the GlobalScope just launches the co-routine into the void. As start and resume do work, there doesn't seem to be a problem with the call or the dispatcher, but it is hard to know where else to look

If I take the contents of the pause or stop co-routine and place it outside the GlobalScope.launch to create something like this:

fun pause() {
    Log.i("CameraPreview.kt", "Pause called");
    GlobalScope.launch(cameraDispatcher) {
        Log.i(TAG, "Pause scope launched, runblocking call ahead.")
        runBlocking {
            Log.i("CameraPreview.kt", "Pause initiated, stop preview state is "+lifecycleState)
            lifecycleState = LifecycleState.PAUSED
            stopPreview()
        }
    }
    cameraState = CameraState.PREVIEW_STOPPING
    cameraApi.stopPreview()
}

Not only does it now work, but the co-routine also starts working on subsequent calls, as though the content of what was being called is somehow getting it jammed up. I see no error messages in Logcat when this is running.

This is my first introduction to Kotlin, so I am not completely sure how it is supposed to work, but it seems as though completely bypassing essential code might not be part of the spec. Can anyone suggest what is happening here or how I could track down the true source of the problem?

The problem is probably that you are using a shared single-thread dispatcher with cameraDispatcher which you then block by using runBlocking . I'm not sure what this code is based in but runBlocking has a warning in its documentation that it should only be used when bridging between coroutines and a strictly thread-bound execution like main or a testing framework.

Your code should still work if you remove all the calls to runBlocking

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM