Android Kotlin(五)数据流StateFlow和LiveData

Android 上的 Kotlin 数据流  

在协程中,与仅返回单个值的挂起函数相反,数据流可按顺序发出多个值。

数据流以协程为基础构建,可提供多个值。

数据流是可通过异步方式进行计算处理的一组数据序列。所发出值的类型必须相同。 

来源标注:Android 上的 Kotlin 数据流  |  Android Developers

书接上篇:Android Jetpack之LiveData 使用及源码


Flow的核心概念 

Flow是一种基于协程的响应式编程库,用于处理异步数据流。

Flow是冷流。即只有在收集端(collect)开始监听时,生产端(emit)才开始执行。

与RxJava相比,Flow的优势在于其与协程的深度集成,提供更加简洁、直观的API。另外,RxJava的Observable是热流,即不论是否有观察者,一旦数据产生就会推送给所有观察者。而Flow的冷流特性更灵活,根据需要按需产生数据,避免了不必要的计算和资源浪费。

在Flow中异常处理是至关重要的一部分。

可以使用协程的异常处理机制,即通过catch操作符捕获流中的异常并进行处理。这种方式使得异常处理更加灵活,同时保持了整体的流畅性。示例代码如下:

fun fetchData(): Flow<Result> = flow {
    try {
        //设置超时时间为5秒
        val data = withTimeout(5000) {
            fetchDataFromNetwork()
        }
        //数据获取过程
        emit(Result.Success(data))
    // 异常处理逻辑
    } catch (e: TimeoutCancellationException) {
        emit(Result.Error("Request timed out"))
    } catch (e: Exception) {
        emit(Result.Error("Failed to fetch data"))
    }
}

Flow的性能优化与背压处理

在面对大规模数据集处理时,如何来保证程序的性能和稳定性?

在处理大规模数据时,可以使用buffer操作符进行性能优化,同时使用onEach进行流的中间处理。

buffer操作符:允许在流中插入一个缓冲区,以缓解生产者和消费者之间的速度不一致的问题。从而提高性能。

val flowWithBuffer: Flow<Data> = fetchData()
    .onEach { data ->
        // 中间处理逻辑
    }
    .buffer() // 使用buffer操作符进行性能优化

另外,在背压处理方面,可以使用conflate操作符

conflate会丢弃掉生产者产生的新数据,只保留最新的数据,从而避免背压。

这样,在数据生产速度大于消费速度时,可以保证消费者只处理最新的数据,避免队列无限增长导致的内存问题。

val conflatedFlow: Flow<Data> = fetchData()
    .onEach { data ->
        // 中间处理逻辑
    }
    .conflate() // 使用conflate操作符进行背压处理

StateFlow

在 Android 中,StateFlow 非常适合需要让可变状态保持可观察的类。

StateFlow 是 Flow 的实现。Flow 是冷流,而StateFlow 是热流和 LiveData 类似。

StateFlow是一种单数据更新的热流,通过emit方法更新StateFlow的数据,通过value属性可以获取当前的数据。

StateFlow的特点

  • 它始终是有值的。
  • 它的值是单一的。
  • 它允许被多个观察者共用 (因此是共享的数据流)。
  • 它永远只会把最新的值重现给订阅者,这与活跃观察者的数量是无关的。 

StateFlow的设计

设计中,关键讲一下MutableStateFlow接口。它接口继承自StateFlow接口,并在此基础上定义了一个新方法compareAndSet, 即通过CAS的方式更新value。代码如下:

public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
    // 当前数据
    public override var value: T
    // 如果except与value相等,则将value更新为update,并返回true
    // 如果except与value不相等,不做任何操作,直接返回false
    public fun compareAndSet(expect: T, update: T): Boolean
}

StateFlow的使用

方式一

在Activity中像类似LiveData 一样的使用 StateFlow。

class DemoViewModel @Inject constructor(val savedState: SavedStateHandle) : BaseViewModel() {
    private val _searchFlow = MutableStateFlow("")
    val searchFlow: StateFlow<String> = _searchFlow
    fun changeSearch(keyword: String) {
        _searchFlow.value = keyword
    }
}
private fun testflow() {
    mViewModel.changeSearch("key")
}
override fun startObserve() {
   lifecycleScope.launchWhenCreated {
       mViewModel.searchFlow.collect {
         LogUtils.w("value $it")
       }
   }
}

方式二

通过一个 冷流 Flow 转换为 StateFlow 。

val stateFlow = flowOf(1, 2, 3).stateIn(
      scope = lifecycleScope,
      started = Lazily,
      initialValue = 1
)
lifecycleScope.launch {
      stateFlow.collect {}
}

LiveData 与 StateFlow

使用 StateFlow 替代 LiveData 应该是目前很多开发者的呼吁了,确实 LiveData 的功能 StateFlow 都能实现,可以说是 LiveData 的升级版。

但请注意,StateFlow 和 LiveData 的行为确实有所不同:

  • StateFlow 需要将初始状态传递给构造函数,而 LiveData 不需要。
  • 当 View 进入 STOPPED 状态时,LiveData.observe() 会自动取消注册使用方,而从 StateFlow 或任何其他数据流收集数据的操作并不会自动停止。

先看看它们的代码的用法。

class DemoViewModel @Inject constructor(val savedState: SavedStateHandle) : BaseViewModel() {
    private val _searchLD = MutableLiveData<String>()
    val searchLD: LiveData<String> = _searchLD

    private val _searchFlow = MutableStateFlow("")
    val searchFlow: StateFlow<String> = _searchFlow

    fun changeSearch(keyword: String) {
        _searchFlow.value = keyword
        _searchLD.value = keyword
    }
}

调用触发与接收事件:

private fun testflow() {
    mViewModel.changeSearch("key")
}
override fun startObserve() {
   mViewModel.searchLD.observe(this){
     LogUtils.w("value $it")
   }

   lifecycleScope.launchWhenCreated {
     mViewModel.searchFlow.collect {
      LogUtils.w("value $it")
     }
   }
}

可以看到,代码使用上十分类似。

LiveData的缺点

  • LiveData在某些特定的场景下会丢失数据
  • LiveData 只能在主线程不能方便地支持异步化
  • LiveData 的数据变换能力远远不如 Flow
  • LiveData 粘性问题解决需要额外扩展
  • LiveData 多数据源的合流能力远远不如 Flow
  • LiveData 默认不支持防抖,值没有变化也会通知

两者在开发中如何选择呢?

  • 团队内全部是Koltin代码开发,推荐使用Flow。因为基于Kotlin代码、基于协程。但是现在很多项目还是 Java 语言开发的。那么LiveData还是很香的。
  • LiveData的学习成本与协程、Flow 的学习成本不可同日而语,开发项目是整个团队的事情,不能说你一个人会一个人用,目前LiveData的简单学习成本是很有优势的。

StateFlow与SharedFlow

StateFlow和SharedFlow有哪些区别?在什么场景下选择StateFlow而不是SharedFlow?

StateFlow是一种具有单一值状态的Flow,主要用于处理单一状态的场景。例如ViewModel中的UI状态。而SharedFlow允许有多个订阅者,并能缓存一定数量的最新元素,适用于多个订阅者需要获取历史元素的场景。

在选择使用StateFlow还是SharedFlow时,需要考虑到是否需要在订阅者之间共享历史元素。如果只关心最新状态,使用StateFlow更为合适;如果需要获取历史元素,或者存在多个订阅者,就可以选择使用SharedFlow。

StateFlow在多线程环境中如何确保线程安全?在不同协程中更新StateFlow会有什么问题?

StateFlow本身并没有对线程的调度进行限制,因此在多线程环境中,需要在合适的协程上下文中使用StateFlow。通常建议在主线程上更新StateFlow,以确保UI的线程安全性。

在不同协程中更新StateFlow可能会导致“竞态条件”,因此需要确保在更新StateFlow时使用适当的同步机制,例如非阻塞式的锁:Mutex。

class MyViewModel : ViewModel() {
    private val _currentState = MutableStateFlow<State>(InitialState)
    val currentState: StateFlow<State> get() = _currentState
//使用Mutex确保在不同协程中更新StateFlow时的同步性,可以有效避免竞态条件。
    private val stateMutex = Mutex()
    fun updateState(newState: State) {
        viewModelScope.launch {
            stateMutex.withLock {
                _currentState.value = newState
            }
        }
    }
}

在使用SharedFlow时,是否存在热启动的问题?如何处理在订阅前产生的事件?

SharedFlow在订阅者加入后才开始产生事件,因此可能存在热启动问题。即在订阅前产生的事件会被忽略。

为了解决这个问题,可以使用stateIn操作符来创建一个StateFlow,并在需要时将其转换为SharedFlow。这样,可以确保在订阅者加入前就开始产生事件,避免热启动问题。

val sharedFlow: SharedFlow<Data> = fetchData()
    .stateIn(viewModelScope, SharingStarted.Eagerly, initialValue)
    .asSharedFlow()

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Kotlin StateFlowKotlin协程库中的一种流(Flow)实现,用于在异步场景下处理状态的变化。StateFlow可用于代替LiveData或RxJava的Observable,提供了一种简洁且类型安全的方式来观察和响应状态的变化。 StateFlow是一个具有状态的流(flow),它可以发射新的值并保持最新的状态。与普通的Flow相比,StateFlow更适用于表示单一的可变状态,并且可以方便地在多个观察者之间共享。 StateFlow在使用上类似于普通的Flow,你可以使用`stateIn`函数将其转换为一个只读的SharedFlow,并使用`collect`或`conflate`等操作符来观察和处理状态的变化。 下面是一个使用StateFlow的示例代码: ``` import kotlinx.coroutines.* import kotlinx.coroutines.flow.* fun main() = runBlocking { val state = MutableStateFlow("Initial state") val job = launch { state.collect { value -> println("Received state: $value") } } state.value = "Updated state" job.cancel() } ``` 在上面的示例中,我们创建了一个MutableStateFlow对象并初始化为"Initial state"。然后使用`collect`函数来观察state的变化,并在状态发生变化时打印出新的值。我们通过修改`state.value`来更新状态,并在控制台上看到"Received state: Updated state"的输出。 总之,Kotlin StateFlow提供了一种方便的方式来处理状态的变化,并与Kotlin协程无缝集成,使得在异步场景下处理状态变化更加简洁和可靠。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾阳Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值