Android LiveData和Coroutines-这是反模式吗



我从一位前开发人员那里继承了一个Kotlin Android项目。例如,他使用挂起功能来处理网络请求。这种功能的一个例子可能是:

suspend fun performNetworkCall(param1: Long, param2: String): ResultOfCall
{
Do()
The()
Stuff()
return theResult
}

到目前为止,一切都很好。现在,他的片段有了ViewModels,并且在这些模型中也有一些方法,这些方法应该异步调用上面的suspend函数并返回一些结果。他是这样做的:

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)
}
}

我对整个可观察/协同事件还不是很有经验,所以我对这种方法的问题是:

  1. 这不是会堆积一大堆LiveData对象吗?由于观察者仍在连接,这些对象不会被释放
  2. 这种方法不好吗?糟糕到可以重构吗?在应该重构的情况下,应该如何重构

我不是这方面的专家,所以我只是从我对协同程序和LiveData如何基于浏览源代码工作的理解出发。

一个典型的LiveData并不能通过观察者来保持活力。它就像任何其他通过被引用而保持活力的典型对象一样。

然而,一旦启动,CoroutineLiveData将通过其协同程序的延续而保持活力。我认为协程系统使用suspend函数来维护对对象的强引用,直到suspend功能返回并且可以删除continuation。因此,fetchSomeData函数创建的LiveData的每个实例都将运行到完成,即使观察者已经到了生命的尽头。当网络调用完成时,没有任何内容可以保存对LiveData的引用,因此应该将其从内存中清除。

所以,这只是暂时的泄漏。如果发出请求的Fragment在收到结果之前关闭,则不会取消您的网络呼叫。这是因为CoroutineLiveData使用自己的内部CoroutineScope,它不与任何生命周期绑定。如果您多次重新打开Fragment,例如通过旋转屏幕,您可能会有多个过时的网络请求仍在运行。显然有一些手动取消的方法,但有点混乱。

此外,在我看来,使用LiveData获取单个结果只是插入了额外的复杂性,并牺牲了自动取消,而您可以直接调用suspend函数。像Reform这样的现代版本的库已经有了用于发出请求的挂起函数,因此如果在与生命周期相关的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)
}
}

最新更新