Можно ли приостановить coroutine в инициализаторе" by lazy"? Я получаю ошибки " runBlocking is not allowed in Android main looper thread"


У меня большая часть моего приложения отлично работает с" ленивыми " инициализаторами, потому что все волшебным образом происходит в нужном порядке.

, но не все инициализаторы являются синхронными. Некоторые из них оборачивают обратные вызовы, что означает, что мне нужно подождать, пока обратный вызов не произойдет, что означает, что мне нужны runBlocking и suspendCoroutine.

Но после рефакторинга всего, я получаю это IllegalStateException: runBlocking is not allowed in Android main looper thread

Что? Ты не можешь блокировать? Ты убиваешь меня здесь. Какой же правильный путь, если мой " ленивый" может быть, это блокирующая функция?

private val cameraCaptureSession: CameraCaptureSession by lazy {
    runBlocking(Background) {
        suspendCoroutine { cont: Continuation<CameraCaptureSession> ->
            cameraDevice.createCaptureSession(Arrays.asList(readySurface, imageReader.surface), object : CameraCaptureSession.StateCallback() {
                override fun onConfigured(session: CameraCaptureSession) {
                    cont.resume(session).also {
                        Log.i(TAG, "Created cameraCaptureSession through createCaptureSession.onConfigured")
                    }
                }

                override fun onConfigureFailed(session: CameraCaptureSession) {
                    cont.resumeWithException(Exception("createCaptureSession.onConfigureFailed")).also {
                        Log.e(TAG, "onConfigureFailed: Could not configure capture session.")
                    }
                }
            }, backgroundHandler)
        }
    }
}

Полная суть класса, чтобы получить представление о том, что я первоначально пытался сделать: https://gist.github.com/salamanders/aae560d9f72289d5e4b49011fd2ce62b

1 4

1 ответ:

Хорошо известно, что выполнение блокирующего вызова в потоке пользовательского интерфейса приводит к полной заморозке приложения на время выполнения вызова. В документации createCaptureSession конкретно говорится

Для завершения настройки сеанса может потребоваться несколько сотен миллисекунд, поскольку аппаратное обеспечение камеры может потребоваться включить или перенастроить.

Это может очень легко привести к появлению диалога Application Not Responding, и ваше приложение будет убито. Вот почему Котлин ввел явная защита от runBlocking в потоке пользовательского интерфейса.

Поэтому ваша идея запустить этот процесс как раз вовремя, когда вы уже пытались получить доступ к cameraCaptureSession, не может сработать. Вместо этого вы должны обернуть код, который обращается к нему, в launch(UI) и превратить ваш val в suspend fun.

В двух словах:

private var savedSession: CameraCaptureSession? = null

private suspend fun cameraCaptureSession(): CameraCaptureSession {
    savedSession?.also { return it }
    return suspendCoroutine { cont ->
        cameraDevice.createCaptureSession(listOf(readySurface, imageReader.surface), object : CameraCaptureSession.StateCallback() {
            override fun onConfigured(session: CameraCaptureSession) {
                savedSession = session
                Log.i(TAG, "Created cameraCaptureSession through createCaptureSession.onConfigured")
                cont.resume(session)
            }

            override fun onConfigureFailed(session: CameraCaptureSession) {
                Log.e(TAG, "onConfigureFailed: Could not configure capture session.")
                cont.resumeWithException(Exception("createCaptureSession.onConfigureFailed"))
            }
        })
    }
}

fun useCamera() {
    launch(UI) {
        cameraCaptureSession().also { session ->
            session.capture(...)
        }
    }
}
Обратите внимание, что session.capture() является еще одной целью для обертывания в suspend fun.

Также не забудьте отметить, что код, который я дал, безопасен только в том случае, если вы можете гарантировать, что не будете звонить. cameraCaptureSession() еще раз, прежде чем первый звонок возобновится. Проверьте поток followup для более общего решения, которое заботится об этом.