LiveData,StateFlow,SharedFlow 使用场景对比

1,344 阅读4分钟

LiveData的优点

1.LiveData可以用在Java层面上
2.LiveData使用简单,可以感知生命周期
3.LiveData可以自动取消订阅

LiveData的缺陷

1.LiveData只能在主线程更新数据
2.LiveData的操作符不够强大,在处理复杂数据流时有些捉襟见肘(虽然MediaorLiveData支持map、switchMap操作符,但也只能在主线程操作)
3.LiveData在子线程多次调用PostValue会导致数据丢失\

LiveData操作符的使用

mediatorData.addSource(likeNum) { t ->
    Log.e(TAG, "mediator data receive likeNum=${t}")
    mediatorData.value = t.toString()
}
mediatorData.addSource(flowNum) { t ->
    Log.e(TAG, "mediator data receive flowNum=${t}")
    mediatorData.value = t.toString()
}
mediatorData.observe(this, Observer<String> {
    Log.e(TAG, "mediator data value=${it}")
})

transData = Transformations.map(
    likeNum
) { return@map it.toString() + "map operator" }

switchData = Transformations.switchMap(
    likeNum
) { input -> MutableLiveData<Double>(input?.plus(4)?.toDouble()) }

transData.observe(this, Observer<String> {
    Log.e(TAG, "transData=${it}")
})

switchData.observe(this, Observer<Double> {
    Log.e(TAG, "switchData=${it}")
})

findViewById<AppCompatTextView>(R.id.text).setOnClickListener {
    likeNum.value = 2
    flowNum.value = 3
}

为什么要引入Flow

liveData不支持切换线程,所有数据转换都在主线程完成;RxJava入门门槛较高,同时需要自己处理生命周期感知,在生命周期结束时取消订阅;Flow刚好介于两者之间,Flow操作符简单,也支持线程切换、背压,同时又属于Kotlin协程的一部分,可以跟协程的基础设施结合

flow的创建

  1. val flow = flowOf{}
  2. val flow = listOf(1,2,3).asFlow()
  3. val flow = channelFlow{}

flow的操作符

中间操作符

  1. map
  2. onEach
  3. transform
  4. take
  5. buffer
  6. onCompletion
  7. catch 代码示例
runBlocking {
    flowOf(1, 2)
        .onEach {    // 发送流之前做的事
            println(it)
            delay(100)
        }
        .map { value -> value + 1 }  // 对流中的数据进行转换
        .transform<Int, String> {    // 重新创建流并改变流的数据类型
            val result = it.toString() + "1"
            emit(result)
        }
        .take(1)        // 取流中的第一条数据
        .onCompletion { cause ->   // 处理流完成的回调
            run {
                if (cause != null) {
                    println("exception ${cause.message}")
                } else {
                    println("on Complete")
                }
            }
        }
        .collect {   // 触发流发送的操作
            println(it)
        }
}

末端流操作符

  1. collect
  2. toList/toMap
  3. reduce
val mainScope = MainScope()
mainScope.launch {
    val ret = countdown(60_000, 2_000) { io(it) }
        .onStart { Log.v("test", "countdown start") }
        .onCompletion { Log.v("test", "countdown end") }
        .reduce { acc, value -> acc + value } // 终端消费者:计算所有异步结果的和
    // 因为 reduce() 是一个 suspend 方法,所以会挂起协程,直到倒计时完成才打印所有异步结果的和
    Log.v("test", "coutdown acc ret = $ret")
}

Flow的冷流

Flow 是冷流,冷流不会发射数据,直到它被收集,所以冷流是“声明式的”,所有能触发收集数据动作的消费者称为终端消费者,所有终端消费的方法都是suspend方法,必须在协程执行

// 执行式的 
suspend fun get(): List<String> =
listof("a", "b", "c").onEach { 
delay(1000)
print(it) 
} 
// 声明式的
fun get(): Flow<String> = 
flowOf("a", "b", "c").onEach 
{ delay(1000) 
print(it) 
}

Flow的线程切换

已搜索关键字来展示结果的场景为例子

editText.textChangeFlow() // 构建输入框文字变化流
    .filter { it.isNotEmpty() } // 过滤空内容,避免无效网络请求
    .debounce(300) // 300ms防抖
    .flatMapLatest { searchFlow(it.toString()) } // 新搜索覆盖旧搜索
    .flowOn(Dispatchers.IO) // 让搜索在异步线程中执行
    .onEach { updateUi(it) } // 获取搜索结果并更新界面
    .launchIn(MainScope()) // 在主线程收集搜索结果
// 构建输入框文字变化流
fun EditText.textChangeFlow(): Flow<CharSequence> = callbackFlow {
    // 构建输入框监听器
    val watcher = object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {}
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

        // 在文本变化后向流发射数据
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            s?.let { offer(it) }
        }
    }
    addTextChangedListener(watcher) // 设置输入框监听器
    awaitClose { removeTextChangedListener(watcher) } // 阻塞以保证流一直运行
}
// 更新界面
fun updateUi(it: List<String>?) {}

// 访问网络进行搜索
fun search(key: String): List<String>? {
    //TODO 网络请求
    return null
}

// 将搜索关键词转换成搜索结果流
fun searchFlow(key: String) = flow { emit(search(key)) }

Flow的一种实现方式SharedFlow

由于flow是冷流,冷流订阅者是一对一的关系当 热流有多个订阅者时,热流订阅者们的关系是一对多的关系,可以与多个订阅者共享信息。

shareFlow的构造函数

public fun <T> MutableSharedFlow( replay: Int = 0, 
extraBufferCapacity: Int = 0, 
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND ): MutableSharedFlow<T>

1.replay表示当新的订阅者Collect时,发送几个已经发送过的数据给它,默认为0,即默认新订阅者不会获取以前的数据
2.extraBufferCapacity表示减去replayMutableSharedFlow还缓存多少数据,默认为0
3.onBufferOverflow表示缓存策略,即缓冲区满了之后Flow如何处理,默认为挂起

whilesubscribed策略

whileSubscribed 策略会在没有收集器的情况下取消上游数据流。通过 stateIn 运算符创建的 StateFlow 会把数据暴露给视图 (View),同时也会观察来自其他层级或者是上游应用的数据流。让这些流持续活跃可能会引起不必要的资源浪费,例如一直通过从数据库连接、硬件传感器中读取数据等等。当您的应用转而在后台运行时,您应当保持克制并中止这些协程

whileSubscribed构造函数中两个重要的参数

  • 1.stopTimeoutMillis 控制一个以毫秒为单位的延迟值,指的是最后一个订阅者结束订阅与停止上游流的时间差。默认值是 0 (立即停止).这个值非常有用,因为您可能并不想因为视图有几秒钟不再监听就结束上游流。这种情况非常常见——比如当用户旋转设备时,原来的视图会先被销毁,然后数秒钟内重建。

  • 2.replayExpirationMillis表示数据重播的过时时间,如果用户离开应用太久,此时您不想让用户看到陈旧的数据,你可以用到这个参数

在页面中观察StateFlow(LiveData)

观察StateFlow需要在协程中,因此我们需要协程构建器,一般我们会使用下面几种

  1. lifecycleScope.launch : 立即启动协程,并且在本 ActivityFragment 销毁时结束协程。
  2. LaunchWhenStartedLaunchWhenResumed,它会在lifecycleOwner进入X状态之前一直等待,又在离开X状态时挂起协程

StateFlow 与 LiveData 有一些相同点

  • 提供「可读可写」和「仅可读」两个版本(StateFlowMutableStateFlow
  • 它的值是唯一的
  • 它允许被多个观察者共用 (因此是共享的数据流)
  • 它永远只会把最新的值重现给订阅者,这与活跃观察者的数量是无关的
  • 支持 DataBinding

它们也有些不同点:

  • 必须配置初始值
  • value 空安全
  • 防抖

state_flow_lifecycle.png

onCreateView(...) { 
   viewLifecycleOwner.lifecycleScope.launch {         
   viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) { 
   myViewModel.myUiState.collect { ... }
    } 
  } 
}

参考文献

mp.weixin.qq.com/s/GVPEcVfmF…

mp.weixin.qq.com/s/-G5AsB5iS…

mp.weixin.qq.com/s/fIh8l8nfJ…

mp.weixin.qq.com/s/rlqEPxais…