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

How to update UI in coroutines in Kotlin 1.3

我正在尝试调用 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)
            }

        })
    }
}

我是这样称呼它的:

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

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

com.google.maps.api.android.lib6.common.apiexception.c: Not on the main thread

我已经尝试了 this 解决方案,但是 Continuation<T> class 中的 resume 自 Kotlin 1.3

以来是 "deprecated"

如果您使用协程-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
        }) 

要回答您眼前的问题,您只需在正确的上下文中启动协程即可:

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

但是,这只是冰山一角,因为您的方法是错误的协程使用方法。它们的主要好处是避免回调,但您正在重新引入它们。您还通过使用不适合生产使用的 GlobalScope 侵犯了 structured concurrency 最佳实践。

显然你已经有一个异步 API 给你一个 Deferred<RoutesResponse> 你可以 await on。使用方法如下:

scope.launch {
    val resp = ApiClient.getInterface().getRoute(request.getURL()).await()
    updateGui(resp)
}

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

以上语法假定您已经准备好实现 CoroutineScopescope 变量。例如,它可以是您的 Activity:

class MyActivity : AppCompatActivity(), CoroutineScope by MainScope {

    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }
}

MainScope 委托将默认协程调度程序设置为 Dispatchers.Main。这允许您使用简单的 launch { ... } 语法。

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

uiScope.launch {
            withContext(Dispatchers.IO) {
                //Do background tasks...
                withContext(Dispatchers.Main){
                    //Update UI
                }
            }
        }