当从活动、片段或 Android 架构组件 ViewModel 启动协程时,使用绑定到该视图组件生命周期的协程作用域是完全有意义的,以避免泄漏和释放资源,例如在用户离开屏幕时取消网络请求。
但在其他情况下,即使用户离开屏幕,您也不想取消协程,例如当您执行网络请求进行分析或写入数据库时。在这种情况下,是否可以启动带有GlobalScope
的协程?启动这些协程的对象大多是Singletons
的,因此它们无论如何都存在于应用程序的生命周期中,因此没有泄漏的危险,对吧?
Kotlin 文档在 GlobalScope 上非常清楚:
应用程序代码通常应使用应用程序定义的协程范围。强烈建议不要在 GlobalScope 实例上使用异步或启动。
在这些情况下可以使用全球范围吗?如果不是,我的应用程序定义的协程范围应该是什么样子的?
如果你有一个异步工作线程,其生命周期是真正的全局的(它们只在你的进程死亡时死亡/结束),使用GlobalScope
或类似的终身范围,是可以的。
假设您有一个发出请求的活动,但即使该活动消失,实际的网络请求也需要继续,因为您希望在网络最终返回响应时缓存它。
您将向活动/片段添加CoroutineScope
,或者更好地添加到 ViewModel,并让最终将内容放在屏幕上的代码在该范围内运行。当活动/片段/视图模型死亡时,范围将被取消,并且不会尝试在不再存在的屏幕上显示任何内容。
但是,您的片段/活动/视图模型可能会与数据源/存储库通信,该数据源/存储库的生命周期仅在进程死亡时结束。您可以切换到其中的 GlobalScope,以便缓存您的网络响应,即使没有活动/片段/视图模型处于活动状态以在屏幕上显示结果。
class MyViewModel(context: CoroutineContext, repo: MyRepository) : ViewModel() {
private val scope = CoroutineScope(context + SuperviserJob())
override fun onCleared() { scope.cancel() }
fun getDataFromNetwork() {
scope.launch {
myLiveData.value = repo.getDataFromNetwork()
}
}
}
// Singleton class
class MyRepositoryImpl(context: CoroutineContext) : MyRepository {
private val scope = CoroutineScope(context + SupervisorJob())
override suspend fun getDataFromNetwork() : String {
return scope.async { // switch scopes
val data = ... fetch data ...
saveInCache(data)
}.await()
}
}
当您的 ViewModel 结束时(调用onCleared
),MyRepositoryImpl
的getDataFromNetwork
仍然保持运行,如果一切正常,将调用saveInCache
。但是,返回的值不会分配给myLiveData.value
因为 ViewModel 作用域的协程已取消。
鉴于您已经在尝试将其附加到应用程序的生命周期,我建议将作用域传递给您的单例,或者通过它实现协程作用域。不幸的是,在 GlobalScope 上运行协程仍然可能以泄漏告终。 有关更多信息,请参阅Roman Elizarov的这篇精彩文章: https://medium.com/@elizarov/the-reason-to-avoid-globalscope-835337445abc