一旦 API 失败,协程就不会调用 API
Coroutine never calls API after once API is failed
在我的视图模型中,我有一个函数使用协同程序调用 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 未经授权的异常,然后当我按下按钮时,协程就不会被调用并且似乎是 returns 来自缓存的再次错误消息。
用例:
我点击了按钮 -> Api 被调用 -> 获得成功响应
我再次点击 -> Api 被调用 -> 获得成功响应
我断开了与 phone
的互联网连接
我点击了按钮 -> Api 被调用 -> 出现类似 ConnectionError
的异常
我已将 phone 连接到互联网
我点击了按钮 -> Api 没有被调用 -> 出现类似 ConnectionError
的异常
现在即使我的 phone 也有有效的互联网连接,我按下按钮,而不是调用 api 并等待响应,它一次又一次地给我之前失败的响应。
之前我使用的是 Rxjava,我没有遇到过任何这样的问题。我是协程的新手,所以如果有人有任何建议,欢迎您
每当您使用 launch
等协程构建器启动协程时,您需要在给定的 CoroutineScope
中启动它 - 这是由函数 defined as an extension on 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
本身已经是一个范围,因此您首先不需要 launch
in GlobalScope
。您可以简单地在 ViewModel
中写下 launch
而不指定范围(本质上是 this.launch {}
)并且没有上下文传递给它,因为它无论如何都会从范围中获取一个。
另一个问题是您使用常规 Job
作为 CoroutineContext
的一部分。对于您启动的每个协程,此 Job
变为 parent,并且每当 child 协程失败时,例如网络错误,parent Job
将被取消太 - 这意味着您尝试启动任何进一步的 children 也会立即失败,因为您无法在已经失败的 Job
下启动新的 child (请参阅 Job
documentation了解更多详情)。
为了避免这种情况,你可以使用 SupervisorJob
代替,它也可以将你的协程组合在一起作为 children,并在 ViewModel
被清除时取消它们,但是 won ' 如果其 children 之一失败,则会被取消。
因此,快速总结要在代码级别进行的修复:
在 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
而不是实现接口本身,as described here an discussed here.
有很多关于这个主题的阅读,这里有一些我通常推荐的文章:
- 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
...以及 Roman Elizarov 在其博客上的其他所有内容,真的 :)
多一个选择。
如果您使用 androidx.lifecycle:lifecycle-extensions:2.1.0
,ViewModel
现在有 viewModelScope
扩展名 属性 使用 SupervisorJob
默认值。并且ViewModel
清除后会自动清除
在我的视图模型中,我有一个函数使用协同程序调用 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 未经授权的异常,然后当我按下按钮时,协程就不会被调用并且似乎是 returns 来自缓存的再次错误消息。
用例:
我点击了按钮 -> Api 被调用 -> 获得成功响应
我再次点击 -> Api 被调用 -> 获得成功响应
我断开了与 phone
的互联网连接我点击了按钮 -> Api 被调用 -> 出现类似 ConnectionError
的异常我已将 phone 连接到互联网
我点击了按钮 -> Api 没有被调用 -> 出现类似 ConnectionError
的异常现在即使我的 phone 也有有效的互联网连接,我按下按钮,而不是调用 api 并等待响应,它一次又一次地给我之前失败的响应。
之前我使用的是 Rxjava,我没有遇到过任何这样的问题。我是协程的新手,所以如果有人有任何建议,欢迎您
每当您使用 launch
等协程构建器启动协程时,您需要在给定的 CoroutineScope
中启动它 - 这是由函数 defined as an extension on 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
本身已经是一个范围,因此您首先不需要 launch
in GlobalScope
。您可以简单地在 ViewModel
中写下 launch
而不指定范围(本质上是 this.launch {}
)并且没有上下文传递给它,因为它无论如何都会从范围中获取一个。
另一个问题是您使用常规 Job
作为 CoroutineContext
的一部分。对于您启动的每个协程,此 Job
变为 parent,并且每当 child 协程失败时,例如网络错误,parent Job
将被取消太 - 这意味着您尝试启动任何进一步的 children 也会立即失败,因为您无法在已经失败的 Job
下启动新的 child (请参阅 Job
documentation了解更多详情)。
为了避免这种情况,你可以使用 SupervisorJob
代替,它也可以将你的协程组合在一起作为 children,并在 ViewModel
被清除时取消它们,但是 won ' 如果其 children 之一失败,则会被取消。
因此,快速总结要在代码级别进行的修复:
在
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
而不是实现接口本身,as described here an discussed here.
有很多关于这个主题的阅读,这里有一些我通常推荐的文章:
- 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
...以及 Roman Elizarov 在其博客上的其他所有内容,真的 :)
多一个选择。
如果您使用 androidx.lifecycle:lifecycle-extensions:2.1.0
,ViewModel
现在有 viewModelScope
扩展名 属性 使用 SupervisorJob
默认值。并且ViewModel
清除后会自动清除