一旦 API 失败,协程从不调用 API



在我的视图模型中,我有一个使用协程调用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本身已经是一个范围,因此您首先不需要GlobalScopelaunch。您可以简单地在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.0ViewModel现在默认使用扩展属性viewModelScopeSupervisorJob。清除后ViewModel会自动清除。

最新更新