【问题标题】:Performance comparison: Mapping a LiveData value vs Flow value性能比较:映射 LiveData 值与流值
【发布时间】:2021-08-11 10:19:34
【问题描述】:

目前,我正在 android 中尝试一些 livedata 和 flow。我不解的是,在映射 livedata 值和 flow 值时是否存在很大的性能差异,因为 livedata 映射是在主 ui 线程中执行的:

/**
 * Returns a [LiveData] mapped from `this` LiveData by applying [transform] to each value set on
 * `this` LiveData.
 *
 * This method is analogous to [io.reactivex.Observable.map].
 *
 * [transform] will be executed on the main thread.
 *
 * [...]
 **/
public inline fun <X, Y> LiveData<X>.map(crossinline transform: (X) -> Y): LiveData<Y> =
    Transformations.map(this) { transform(it) }

现在是诡计部分:假设我们有一个要映射的 livedata 值。由于映射是在 ui 线程中执行的,我们将 livedata 值转换为流值:

public fun <T> LiveData<T>.asFlow(): Flow<T> = flow {
    val channel = Channel<T>(Channel.CONFLATED)
    val observer = Observer<T> {
        channel.offer(it)
    }
    withContext(Dispatchers.Main.immediate) {
        observeForever(observer)
    }
    try {
        for (value in channel) {
            emit(value)
        }
    } finally {
        GlobalScope.launch(Dispatchers.Main.immediate) {
            removeObserver(observer)
        }
    }
}

将 livedata 观察者转换为流后,我们将值成功映射到另一个线程 (Dispatchers.IO)。但是现在我们不需要 ui 的流,所以我们将其转换回 livedata 值:

@JvmOverloads
public fun <T> Flow<T>.asLiveData(
    context: CoroutineContext = EmptyCoroutineContext,
    timeoutInMs: Long = DEFAULT_TIMEOUT
): LiveData<T> = liveData(context, timeoutInMs) {
    collect {
        emit(it)
    }
}

现在问题:

  1. 与映射流值相比,映射 livedata 值是否对性能影响较大
  2. 将 livedata 值转换为流值,然后将其转换回 livedata 是否会引入任何开销和/或性能问题?如果是,直接映射 livedata 值会更好吗?

一个映射示例是:

fun LiveData<WorkInfo>.collectStatus(): LiveData<Status<Unit>> = map { workInfo ->
    when(workInfo.state) {
        WorkInfo.State.ENQUEUED -> Status.loading()

        WorkInfo.State.RUNNING -> Status.loading()

        WorkInfo.State.SUCCEEDED -> Status.success(Unit)

        WorkInfo.State.BLOCKED -> Status.failed("Workmanager blocked")

        WorkInfo.State.FAILED -> Status.failed("Workmanager failed")

        WorkInfo.State.CANCELLED -> Status.failed("Workmanager cancelled")
    }
}

【问题讨论】:

  • 这取决于你所说的性能。除了线程切换的相同工作之外,还有微不足道的额外开销。但是,如果在工作完成时 UI 动画正在进行,则在主线程之外进行工作会使 UI 动画更流畅,前提是有足够的工作要做才能被察觉。我认为 LiveData 和 Flows 之间的性能没有任何显着差异,但使用纯 LiveData 时,映射到不同线程上的代码可能更加复杂。
  • @Tenfour04 但是switchMapliveData(context) 组合是否切换线程?
  • 是的。 liveData 启动一个在后台运行的协程以发布到 LiveData。
  • 您的映射转换示例的成本约为几纳秒。将任务从一个线程移交给另一个线程的成本在微秒范围内。所以那是一千倍。相比之下,包装/展开是微不足道的。

标签: android kotlin kotlin-coroutines android-livedata


【解决方案1】:

如果你想在不同的线程上执行映射,那么你应该使用LiveData.switchMapliveData(context) { }

livedata.switchMap { 
    liveData(Dispatchers.IO) { 
        emit(someValue)
    }
}

或者摆脱 LiveData 并在任何地方使用 Flow/StateFlow/SharedFlow/Channel

【讨论】:

  • 对于我上面的例子,是否推荐这种上下文切换?什么会提供更好的性能:直接在 ui 线程中映射或首先切换上下文?
  • 为什么你的方法有效?因为在 switchmap 文档中它说,转换仍然在 ui 线程中执行?
猜你喜欢
  • 2015-11-03
  • 1970-01-01
  • 1970-01-01
  • 2019-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-24
相关资源
最近更新 更多