是否可以在 "by lazy" 初始化程序中暂停协程?我收到 "runBlocking is not allowed in Android main looper thread" 的错误
Is it possible to suspendCoroutine in a "by lazy" initializer? I get errors of "runBlocking is not allowed in Android main looper thread"
我的大部分应用程序都可以与 "by lazy" 初始化器一起正常工作,因为一切都神奇地按照必要的顺序发生。
但并不是所有的初始化器都是同步的。其中一些是包装回调,这意味着我需要等到回调发生,这意味着我需要 runBlocking
和 suspendCoroutine
.
但是在重构所有内容之后,我得到了这个 IllegalStateException: runBlocking is not allowed in Android main looper thread
什么?你不能阻止?你在这里杀了我。如果我的"by lazy"恰好是阻塞函数,正确的做法是什么?
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)
}
}
}
class 的完整 GIST,了解我最初想要完成的事情:https://gist.github.com/salamanders/aae560d9f72289d5e4b49011fd2ce62b
众所周知,在 UI 线程上执行阻塞调用会导致应用程序在调用期间完全冻结。 createCaptureSession
的文档明确指出
It can take several hundred milliseconds for the session's configuration to complete, since camera hardware may need to be powered on or reconfigured.
它可能很容易导致 Application Not Responding
对话框和您的应用程序被终止。这就是为什么 Kotlin 在 UI 线程上引入了针对 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()
时,我提供的代码才是安全的。查看 以获取更通用的解决方案。
我的大部分应用程序都可以与 "by lazy" 初始化器一起正常工作,因为一切都神奇地按照必要的顺序发生。
但并不是所有的初始化器都是同步的。其中一些是包装回调,这意味着我需要等到回调发生,这意味着我需要 runBlocking
和 suspendCoroutine
.
但是在重构所有内容之后,我得到了这个 IllegalStateException: runBlocking is not allowed in Android main looper thread
什么?你不能阻止?你在这里杀了我。如果我的"by lazy"恰好是阻塞函数,正确的做法是什么?
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)
}
}
}
class 的完整 GIST,了解我最初想要完成的事情:https://gist.github.com/salamanders/aae560d9f72289d5e4b49011fd2ce62b
众所周知,在 UI 线程上执行阻塞调用会导致应用程序在调用期间完全冻结。 createCaptureSession
的文档明确指出
It can take several hundred milliseconds for the session's configuration to complete, since camera hardware may need to be powered on or reconfigured.
它可能很容易导致 Application Not Responding
对话框和您的应用程序被终止。这就是为什么 Kotlin 在 UI 线程上引入了针对 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()
时,我提供的代码才是安全的。查看