kotlin - Android LiveData 和 Coroutines - 这是一种反模式吗?
问题描述
我从一位前开发人员那里继承了一个 Kotlin Android 项目。例如,他使用挂起函数来处理网络请求。这种函数的一个例子可能是这样的:
suspend fun performNetworkCall(param1: Long, param2: String): ResultOfCall
{
Do()
The()
Stuff()
return theResult
}
到现在为止还挺好。现在他ViewModel
的片段有 s 并且他在这些模型中也有方法,这些方法应该异步调用上述挂起函数并返回一些结果。他这样做:
sealed class LiveDataResult<out R>
{
data class Success<T>(val result: T) : LiveDataResult<T>()
data class Failure(val errorMessage: String) : LiveDataResult<Nothing>()
}
fun fetchSomeData(param1: Long, param2: String): LiveData<LiveDataResult<String>> = liveData {
val resultOfCall = performNetworkCall(param1, param2)
if (resultOfCall indicates success)
emit(LiveDataResult.Success("Yay!")
else
emit(LiveDataResult.Failure("Oh No!")
}
在他的片段中,他称这样的方法为
viewModel.fetchSomeData(0, "Call Me").observe(viewLifecycleOwner) {
when (it)
{
is LiveDataResult.Success -> DoSomethingWith(it.result)
is LiveDataResult.Failure -> HandleThe(it.errorMessage)
}
}
我对整个可观察/协程问题还不是很有经验,所以我对这种方法的问题是:
- 这不会堆积一大堆
LiveData
由于观察者仍然被连接而不会被释放的对象吗? - 这是一个不好的方法吗?糟糕到需要重构?一个应该如何重构以防它被重构?
解决方案
我不是这方面的专家,所以我只是从我对协程和 LiveData 如何基于浏览源代码工作的理解来说。
一个典型的 LiveData 并不能通过观察者来保持活力。它就像任何其他典型的对象一样,通过被引用保持活力。
然而,CoroutineLiveData 一旦启动,将通过其协程延续保持活动状态。我认为协程系统通过挂起函数保持对对象的强引用,直到挂起函数返回并且可以删除延续。因此,该函数创建的每个 LiveData 实例都fetchSomeData
将运行到完成,即使观察者已经达到生命的尽头。当网络调用完成时,没有任何东西可以保存对 LiveData 的引用,因此应该从内存中清除它。
因此,只有暂时的泄漏发生。如果发出请求的 Fragment 在收到结果之前关闭,您的网络调用不会被取消。这是因为 CoroutineLiveData 使用自己的内部 CoroutineScope,它不依赖于任何生命周期。如果您多次重新打开 Fragment,例如通过旋转屏幕,您可能有多个过时的网络请求仍在运行。显然有手动取消的解决方法,但这有点混乱。
此外,在我看来,当您可以直接调用挂起函数时,使用 LiveData 获取单个结果只是增加了额外的复杂性并牺牲了自动取消。现代版本的库(如 Retrofit)已经具有用于发出请求的挂起函数,因此如果在与生命周期相关的 CoroutineScope 上调用挂起函数,则会自动取消网络请求。
支持自动取消的此代码的重构版本可能如下所示:
suspend fun performNetworkCall(param1: Long, param2: String): ResultOfCall
{
val result = setUpAndDoSomeRetrofitSuspendFunctionCall(param1, param2)
return result
}
sealed class NetworkResult<out R>
{
data class Success<T>(val result: T) : NetworkResult<T>()
data class Failure(val errorMessage: String) : NetworkResult<Nothing>()
}
suspend fun fetchSomeData(param1: Long, param2: String): NetworkResult<String>
{
val resultOfCall = performNetworkCall(param1, param2)
return if (resultOfCall indicates success)
NetworkResult.Success("Yay!")
else
NetworkResult.Failure("Oh No!")
}
// In Fragment:
lifecycleScope.launchWhenStarted
{
when (val result = viewModel.fetchSomeData(0, "Call Me"))
{
is NetworkResult.Success -> DoSomethingWith(result.result)
is NetworkResult.Failure -> HandleThe(result.errorMessage)
}
}
推荐阅读
- mysql - 我可以使用我的凭据登录到远程 phpmyadmin,但是当我尝试连接到 phpstorm 中的数据库时,我收到拒绝密码
- object - Kotlin/Singleton 中的对象 -> 不同的实例(?)
- c++ - c ++计算数字之间的空格(不是字符串)
- javascript - Route.post() 需要一个回调函数,但得到一个 [object Undefined] :向我解释
- javascript - 当我使用其他方法时,jQuery 加载动画未隐藏在页面上已加载或显示延迟
- cmake - 如何从 github 源构建和安装 yugabyte db 版本 2.0.6.0?
- object - 如何阻止 Lua 对象构造函数在实例之间共享嵌套表?
- python - 如何在 Jupyter 笔记本降价单元格中将 python 变量插入到 Latex 矩阵中
- c++ - 在 C++ 中,返回一个大向量会导致堆栈中的数据复制吗?
- spring - Spring Boot 2.2.1 在 Build 时创建两个 jar