如何在Kotlin 1.3中更新协程中的UI

Moh*_*sen 12 android ui-thread kotlin kotlin-coroutines

我正在尝试调用API,当我的变量准备就绪时,分别更新UI组件.

这是我的网络单身人士正在启动协程:

object MapNetwork {
    fun getRoute(request: RoutesRequest,
                 success: ((response: RoutesResponse) -> Unit)?,
                 fail: ((throwable: Throwable) -> Unit)? = null) {
        val call = ApiClient.getInterface().getRoute(request.getURL())

        GlobalScope.launch(Dispatchers.Default, CoroutineStart.DEFAULT, null, {

            try {
                success?.invoke(call.await())
            } catch (t: Throwable) {
                fail?.invoke(t)
            }

        })
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是我所说的:

network.getRoute(request,
            success = {
                // Make Some UI updates
            },
            fail = {
                // handle the exception
            }) 
Run Code Online (Sandbox Code Playgroud)

我得到的异常表示无法从UI线程以外的任何线程更新UI:

com.google.maps.api.android.lib6.common.apiexception.c: Not on the main thread
Run Code Online (Sandbox Code Playgroud)

我已经尝试过这个解决方案,但是从Kotlin 1.3开始resume,Continuation<T>课堂上已经"弃用"了

X-B*_*... 12

private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

uiScope.launch {
            withContext(Dispatchers.IO) {
                //Do background tasks...
                withContext(Dispatchers.Main){
                    //Update UI
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)


Mar*_*nik 10

要回答您的直接问题,您必须在正确的上下文中启动协程:

val call = ApiClient.getInterface().getRoute(request.getURL())
GlobalScope.launch(Dispatchers.Main) {
    try {
        success?.invoke(call.await())
    } catch (t: Throwable) {
        fail?.invoke(t)
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,这只是冰山一角,因为你的方法是使用协同程序的错误方法.他们的主要好处是避免回调,但你正在重新引入它们.您还通过使用不适合生产用途的结构化并发最佳实践来侵犯您GlobalScope.

显然,你已经有了一个异步API,让你一个Deferred<RoutesResponse>,你可以await上.使用方法如下:

scope.launch {
    val resp = ApiClient.getInterface().getRoute(request.getURL()).await()
    updateGui(resp)
}
Run Code Online (Sandbox Code Playgroud)

您可能会因为我建议launch在每个GUI回调中都有一个块来执行可挂起的代码而感到苦恼,但实际上这是使用此功能的推荐方法.它与写入严格并行,Thread { ... my code ... }.start()因为launch块的内容将同时运行到它之外的代码.

上面的语法假设您有一个scope实现的变量ready CoroutineScope.例如,它可以是你的Activity:

class MyActivity : AppCompatActivity(), CoroutineScope {
    lateinit var masterJob: Job
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + masterJob

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        masterJob = Job()
    }

    override fun onDestroy() {
        super.onDestroy()
        masterJob.cancel()
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意如何coroutineContext设置默认协程调度程序Dispatchers.Main.这允许您使用普通launch { ... }语法.

  • “ GlobalScope并非用于生产用途。”我很确定,如果您不打算取消工作,那么使用GlobalScope不会有任何问题。 (2认同)
  • 您声称的结果是,Kotlin团队强制执行协程定义范围是错误的,毕竟,结构化并发并不是一回事,而且无论发起者发生什么事情,我们都应该让协程正常运行。我可以根据自己的经验告诉您,结构化并发是天赐之物,在意识到由于协程缠绵而使我一次又一次的崩溃之后,我什至在实现一流并发之前就实现了。 (2认同)
  • 我并不是说提供选项是错误的,不是说在每种情况下都不需要。 (2认同)
  • 您是说不需要,除非您打算取消协程,就好像可以选择一样。您必须_总是_计划取消所有协程,以防活动被破坏,或在任何其他情况下均被视为等同。在Android应用程序中,所有内容的生命周期都由系统决定。 (2认同)

Mik*_*ski 6

如果你正在使用coroutines-android你可以使用Dispatchers.Main
(gradle依赖是implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0")

network.getRoute(request,
        success = {
            withContext(Dispatchers.Main) {
                // update UI here
            }
        },
        fail = {
            // handle the exception
        }) 
Run Code Online (Sandbox Code Playgroud)

  • 为了扩展@ EpicPandaForce的答案,`runOnUiThread`是Android提供的方法,而不是协程.因此它是完全独立的.如果你打算使用协同程序,你应该在任何地方使用它们(因此使用`withContext`).在这种情况下,`runOnUiThread()`效率较低. (3认同)