如何在 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
块的内容将 运行 与它之外的代码同时发生。
以上语法假定您已经准备好实现 CoroutineScope
的 scope
变量。例如,它可以是您的 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
}
}
}
我正在尝试调用 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
如果您使用协程-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
块的内容将 运行 与它之外的代码同时发生。
以上语法假定您已经准备好实现 CoroutineScope
的 scope
变量。例如,它可以是您的 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
}
}
}