在我的视图模型中,我有一个使用协程调用API的函数。
fun loadPosts(){
GlobalScope.launch(coroutineContext){
changeState(load = true)
val list= withContext(Dispatchers.IO) {
apiService.getProfile(AUTH)
}
changeState(false,false,null)
showResult(list)
}
}
每次我点击一个按钮时,这个函数就会被触发,API 被调用,我得到有效的响应。但是一旦我的 api 收到像 500 或 Http 401 未经授权这样的异常,那么当我点击按钮时,协程永远不会被调用,并且似乎它从缓存中返回再次错误消息。
对于用例:
我点击了按钮-> Api被称为->得到了成功的响应
我再次点击了-> Api被称为->得到了成功的响应
我断开了与手机的互联网连接
我点击了按钮 -> API 被称为 ->出现类似连接错误的异常
我已将手机连接到互联网
我点击了按钮 ->API 没有被调用->出现类似连接错误的异常
现在即使我的手机有有效的互联网连接,我按下按钮,而不是调用 api 并等待响应,它一次又一次地给我以前的失败响应。
早些时候我使用的是Rxjava,我没有遇到任何这样的问题。我是协程的新手,所以如果有人有任何建议,欢迎你
每当您使用协程构建器(如launch
(启动协程时,都需要在给定的CoroutineScope
中启动它 - 这是由定义为CoroutineScope
扩展的函数强制执行的。此作用域包含一个CoroutineContext
,它将定义协程的执行方式。
根据上面的评论,我假设您大致使用此设置:
abstract class BaseViewModel : ViewModel(), CoroutineScope {
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCleared() {
coroutineContext.cancel()
}
}
通过使用GlobalScope.launch(coroutineContext)
,您实际上是在用上下文参数覆盖GlobalScope
提供的所有内容。另外,由于您的ViewModel
本身已经是一个范围,因此您首先不需要GlobalScope
launch
。您可以简单地在ViewModel
中写下launch
,没有指定范围(基本上是this.launch {}
(,也没有传递给它的上下文,因为它无论如何都会从范围中获取一个。
另一个问题是您正在使用常规Job
作为CoroutineContext
的一部分。此Job
成为您启动的每个协程的父项,并且每当子协程失败时(例如在网络错误时(,父Job
也会被取消 - 这意味着您尝试启动的任何其他子项也将立即失败,因为您无法在已经失败的Job
下启动新子项(有关详细信息,请参阅Job
文档(。
若要避免这种情况,可以改用SupervisorJob
,该也可以将协程分组为子项,并在清除ViewModel
时取消它们,但如果其中一个子项发生故障,则不会取消它们。
因此,快速总结要在代码级别进行的修复:
在
ViewModel
中使用SupervisorJob
(当你在那里时,只需创建一次组合CoroutineContext
,直接分配它,而不是将其放在getter中(:override val coroutineContext: CoroutineContext = Dispatchers.Main + SupervisorJob()
在
ViewModel
中定义的范围内启动协程,而不是在GlobalScope
中启动协程:abstract class BaseViewModel : ViewModel(), CoroutineScope { // ... fun load() { launch { // equivalent to this.launch, because `this` is a CoroutineScope // do loading } } }
您可能还需要考虑让您的ViewModel
包含CoroutineScope
而不是实现接口本身,如此处所述,此处讨论。
关于这个主题有很多阅读要做,以下是我通常推荐的一些文章:
- https://medium.com/@elizarov/coroutine-context-and-scope-c8b255d59055
- https://medium.com/@elizarov/the-reason-to-avoid-globalscope-835337445abc
- https://proandroiddev.com/demystifying-coroutinecontext-1ce5b68407ad
。以及罗曼·伊利扎罗夫在他的博客上所做的其他一切,真的很:)
还有一个选项。
如果使用androidx.lifecycle:lifecycle-extensions:2.1.0
,ViewModel
现在默认使用扩展属性viewModelScope
SupervisorJob
。清除后ViewModel
会自动清除。