Kotlin StateFlow、SharedFlow、Channel解析

StateFlowSharedFlow 是 Flow API,允许数据流以最优方式发出状态更新并向多个使用方发出值。

StateFlow

官方文档解释:StateFlow 是一个状态容器式可观察数据流,可以向其收集器发出当前状态更新和新状态更新。还可通过其 value 属性读取当前状态值。如需更新状态并将其发送到数据流,请为 MutableStateFlow 类的 value 属性分配一个新值。

StateFlow有两种类型: StateFlow 和 MutableStateFlow :

public interface StateFlow<out T> : SharedFlow<T> {
    public val value: T
}

public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
    public override var value: T

    public fun compareAndSet(expect: T, update: T): Boolean
}

状态由其值表示。任何对值的更新都会反馈新值到所有流的接收器中。

StateFlow 基本使用
class StateFlow {

    private val _state = MutableStateFlow<String>("unKnown")
    val state: kotlinx.coroutines.flow.StateFlow<String> get() = _state

    fun getApi(scope: CoroutineScope) {
        scope.launch {
            var res = getApi()
            _state.value = res
        }
    }

    /**
     * 进行网络Api请求
     */
    private suspend fun getApi() = withContext(Dispatchers.IO) {
        delay(2000) //模拟耗时请求
        "result"
    }

}

    private fun stateFlowTest() = runBlocking {
        val stateFlow = StateFlow()
        stateFlow.getApi(this) //开始获取结果

        launch(Dispatchers.IO) {
            stateFlow.state.collect {
                Log.i("minfo", "${Thread.currentThread().name} + $it")
            }
        }

        launch(Dispatchers.IO) {
            stateFlow.state.collect {
                Log.i("minfo", "${Thread.currentThread().name} + $it")

            }
        }
    }

打印结果:

 I  DefaultDispatcher-worker-1 + unKnown
 I  DefaultDispatcher-worker-2 + unKnown
// 等待两秒
 I  DefaultDispatcher-worker-1 + result
 I  DefaultDispatcher-worker-1 + result

StateFlow 的使用方式与 LiveData 类似。
MutableStateFlow 是可变类型的,即可以改变 value 的值。 StateFlow 则是只读的。这与 LiveData、MutableLiveData一样。

为什么使用 StateFlow

我们知道 LiveData 有如下特点:

1.只能在主线程更新数据,即使在子线程通过 postValue()方法,最终也是将值 post 到主线程调用的 setValue()
2.LiveData 是不防抖的
3.LiveData 的 transformation 是在主线程工作
4.LiveData 需要正确处理 “粘性事件” 问题。
鉴于此,使用 StateFlow 可以轻松解决上述场景。

class StateFlow {

    private val _state = MutableStateFlow<String>("unKnown")
    val state: kotlinx.coroutines.flow.StateFlow<String> get() = _state

    fun getApi2(scope: CoroutineScope) {
        scope.launch {
            delay(2000)
            _state.value = "hello, coroutine"
        }
    }

    fun getApi3(scope: CoroutineScope) {
        scope.launch {
            delay(2000)
            _state.value = "hello, kotlin"
        }
    }
}

    fun stateFlowTest2() = runBlocking<Unit> {
        val stateFlow: StateFlow = StateFlow()

        stateFlow.getApi2(this) // 开始获取结果
        delay(1000)
        stateFlow.getApi3(this) // 开始获取结果

        val job1 = launch(Dispatchers.IO) {
            delay(8000)
            stateFlow.state.collect {
                Log.i("minfo", "${Thread.currentThread().name} + $it")
            }
        }
        val job2 = launch(Dispatchers.IO) {
            delay(8000)
            stateFlow.state.collect {
                Log.i("minfo", "${Thread.currentThread().name} + $it")
            }
        }

        // 避免任务泄漏,手动取消
        delay(10000)
        job1.cancel()
        job2.cancel()
    }

现在的场景是,先请求 getApi1(), 一秒之后再次请求 getApi2(), 这样 stateFlow 的值加上初始值,一共被赋值过 3 次。确保,三次赋值都完成后,我们再收集 StateFlow 中的数据。
打印结果:

 DefaultDispatcher-worker-1 + hello, kotlin
 DefaultDispatcher-worker-2 + hello, kotlin

结果显示了,StateFlow 只会将最新的数据发射给订阅者。对比 LiveData, LiveData 内部有 version 的概念,对于注册的订阅者,会根据 version 进行判断,将历史数据发送给订阅者。即所谓的“粘性”。而SharedFlow可以做到此种粘性。

如需将任何数据流转换为 StateFlow,请使用stateIn中间运算符。

StateFlow、Flow 和 LiveData

StateFlow 和LiveData具有相似之处。两者都是可观察的数据容器类,并且在应用架构中使用时,两者都遵循相似模式。

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

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

SharedFlow 也有两种类型:SharedFlow 和 MutableSharedFlow。
使用 sharedIn 方法可以将 Flow 转换为 SharedFlow。

public fun <T> MutableSharedFlow(
   replay: Int,   // 当新的订阅者Collect时,发送几个已经发送过的数据给它
   extraBufferCapacity: Int = 0,  // 减去replay,MutableSharedFlow还缓存多少数据
   onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND  // 缓存溢出时的处理策略,三种 丢掉最新值、丢掉最旧值和挂起
): MutableSharedFlow<T>

class SharedFlow {

    private val _state = MutableSharedFlow<Int>(replay = 3,
        extraBufferCapacity = 2)
    val state: MutableSharedFlow<Int> get() = _state

    fun getApi(scope: CoroutineScope) {
        scope.launch {
            for (i in 0..20) {
                delay(200)
                _state.emit(i)
                Log.i("minfo", "send data $i")
            }
        }
    }

    fun main() = runBlocking {
        getApi(this)

        val job = launch(Dispatchers.IO) {
            delay(3000)
            state.collect {
                Log.i("minfo", "collect  $it")
            }
        }
        delay(5000)
        job.cancel()
    }
}

打印结果:

send data 0
send data 1
send data 2
send data 3
send data 4
send data 5
send data 6
send data 7
send data 8
send data 9
send data 10
send data 11
send data 12
send data 13
collect  11
collect  12
collect  13
send data 14
collect  14
send data 15
collect  15
send data 16
collect  16
send data 17
collect  17
send data 18
collect  18
send data 19
collect  19
send data 20
collect  20

分析一下该结果:
SharedFlow 每 200ms 发射一次数据,总共发射 21 个数据出来,耗时大约 4s。
SharedFlow 的 replay 设置为 3, extraBufferCapacity 设置为2, 即 SharedFlow 的缓存为 5 。缓存溢出的处理策略是默认挂起的。
订阅者是在 3s 之后开始收集数据的。此时应该已经发射了 14 个数据,即 0-13, SharedFlow 的缓存为 5, 缓存的数据为 9-13, 但是,只给订阅者发送 3 个旧数据,即订阅者收集到的值是从 11 开始的。

StateFlow 和 SharedFlow 的使用场景

StateFlow 的命名已经说明了适用场景, StateFlow 只会向订阅者发射最新的值,适用于对状态的监听。
SharedFlow 可以配置对历史发射的数据进行订阅,适合用来处理对于事件的监听。

Channel

Flow底层使用的Channel机制实现,StateFlow、SharedFlow都是一对多的关系,如果上游发送者与下游UI层的订阅者是一对一的关系,可以使用Channel来实现,Channel默认是粘性的。

Channel使用特点:
每个消息只有一个订阅者可以收到,用于一对一的通信。

Channel使用示例:

//viewModel中
private val _loadingChannel = Channel<Boolean>()
val loadingFlow = _loadingChannel.receiveAsFlow()

private suspend fun loadStart() {
    _loadingChannel.send(true)
}

private suspend fun loadFinish() {
    _loadingChannel.send(false)
}

//UI层接收Loading信息
 mViewModel.loadingFlow.flowWithLifecycle2(this, Lifecycle.State.STARTED) { isShow ->
     mStatusViewUtil.showLoadingView(isShow)
 }

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

爬虫Python学习是指学习如何使用Python编程语言来进行网络爬取和数据提取的过程。Python是一种简单易学且功能强大的编程语言,因此被广泛用于爬虫开发。爬虫是指通过编写程序自动抓取网页上的信息,可以用于数据采集、数据分析、网站监测等多个领域。 对于想要学习爬虫的新手来说,Python是一个很好的入门语言。Python的语法简洁易懂,而且有丰富的第三方库和工具,如BeautifulSoup、Scrapy等,可以帮助开发者更轻松地进行网页解析和数据提取。此外,Python还有很多优秀的教程和学习资源可供选择,可以帮助新手快速入门并掌握爬虫技能。 如果你对Python编程有一定的基础,那么学习爬虫并不难。你可以通过观看教学视频、阅读教程、参与在线课程等方式来学习。网络上有很多免费和付费的学习资源可供选择,你可以根据自己的需求和学习风格选择适合自己的学习材料。 总之,学习爬虫Python需要一定的编程基础,但并不难。通过选择合适的学习资源和不断实践,你可以逐步掌握爬虫的技能,并在实际项目中应用它们。 #### 引用[.reference_title] - *1* *3* [如何自学Python爬虫? 零基础入门教程](https://blog.csdn.net/zihong523/article/details/122001612)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [新手小白必看 Python爬虫学习路线全面指导](https://blog.csdn.net/weixin_67991858/article/details/128370135)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值