首页 > 解决方案 > 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)
    }
}

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

  1. 这不会堆积一大堆LiveData由于观察者仍然被连接而不会被释放的对象吗?
  2. 这是一个不好的方法吗?糟糕到需要重构?一个应该如何重构以防它被重构?

标签: kotlinandroid-livedatakotlin-coroutinessuspend

解决方案


我不是这方面的专家,所以我只是从我对协程和 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)
    }
}

推荐阅读