Android LiveData 和协程 - 这是一种反模式吗?

Android LiveData and Coroutines - is this an anti-pattern?

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

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

   return theResult
}

到目前为止,还不错。现在他的片段有 ViewModels,他在这些模型中也有方法可以异步调用上述挂起函数和 return 一些结果。他是这样做的:

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

我对整个 observable/coroutine 问题还不是很有经验,所以我对这种方法的问题是:

  1. 这不会堆积一大堆 LiveData 由于观察者仍处于连接状态而无法释放的对象吗?
  2. 这种方法不好吗?糟糕到需要重构?如果一个人应该重构,应该如何重构?

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

典型的 LiveData 不能通过观察者来保持活力。它就像任何其他通过引用保持活动状态的典型对象。

CoroutineLiveData,但是,一旦启动,将通过其协程延续保持活动状态。我认为协程系统保持对带有suspend函数的对象的强引用,直到suspend函数returns并且continuation可以被丢弃。因此,由 fetchSomeData 函数创建的每个 LiveData 实例都将 运行 完成,即使观察者已到达生命终点。网络调用完成后,没有任何东西可以保存对 LiveData 的引用,因此应该从内存中清除它。

所以,这只是暂时的泄漏。如果发出请求的片段在收到结果之前关闭,您的网络调用将不会被取消。这是因为 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)
    }
}