一旦 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.


有很多关于这个主题的阅读,这里有一些我通常推荐的文章:

...以及 Roman Elizarov 在其博客上的其他所有内容,真的 :)

多一个选择。

如果您使用 androidx.lifecycle:lifecycle-extensions:2.1.0ViewModel 现在有 viewModelScope 扩展名 属性 使用 SupervisorJob 默认值。并且ViewModel清除后会自动清除