朱涛 · Kotlin 编程第一课
朱涛
Google 认证的 Kotlin、Android 开发者专家,博客“Kotlin Jetpack 实战”作者
6717 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 50 讲
朱涛 · Kotlin 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

20 | Flow:为什么说Flow是“冷”的?

你好,我是朱涛。今天我们来学习 Kotlin 协程 Flow 的基础知识。
Flow,可以说是在 Kotlin 协程当中自成体系的知识点。Flow 极其强大、极其灵活,在它出现之前,业界还有很多质疑 Kotlin 协程的声音,认为 Kotlin 的挂起函数、结构化并发,并不足以形成核心竞争力,在异步、并发任务的领域,RxJava 可以做得更好。
但是,随着 2019 年 Kotlin 推出 Flow 以后,这样的质疑声就渐渐没有了。有了 Flow 以后,Kotlin 的协程已经没有明显的短板了。简单的异步场景,我们可以直接使用挂起函数、launch、async;至于复杂的异步场景,我们就可以使用 Flow。
实际上,在很多技术领域,Flow 已经开始占领 RxJava 原本的领地,在 Android 领域,Flow 甚至还要取代原本 LiveData 的地位。因为,Flow 是真的香啊!
接下来,我们就一起来学习 Flow。

Flow 就是“数据流”

Flow 这个单词有“流”的意思,比如 Cash Flow 代表了“现金流”;Traffic Flow 代表了“车流”;Flow 在 Kotlin 协程当中,其实就是“数据流”的意思。因为 Flow 当中“流淌”的,都是数据。
为了帮你建立思维模型,我做了一个动图,来描述 Flow 的行为模式。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Kotlin协程中的Flow是一种强大而灵活的数据流模型,为异步、并发任务提供了便捷的解决方案。Flow的操作符与集合操作符高度一致,使得学习成本较低,同时还提供了特殊的操作符,专门为Flow设计。在Flow的中间操作符中,onStart和onCompletion是比较特殊的,它们是以操作符的形式存在,但实际上的作用是监听生命周期回调。对于异常处理,可以使用catch操作符来捕获上游和中间操作的异常,而try-catch则更适用于下游异常的捕获。Flow在Kotlin协程中具有重要的地位,为异步任务处理提供了便利且强大的工具。 Flow中的线程切换可以使用flowOn、launchIn、withContext等操作符,其中flowOn和launchIn已经可以满足大部分需求。另外,Flow被认为是“冷”的,因为只有调用终止操作符之后,Flow才会开始工作。Flow还具有“懒”的特性,一次只会处理一条数据,这使得它更加灵活和高效。 总的来说,Kotlin协程中的Flow提供了强大的异步数据流处理能力,同时具有灵活的线程切换和懒加载特性,为开发者提供了便捷且高效的编程工具。文章还介绍了Flow在Android中的实际应用场景,展示了如何利用Flow的操作符设计出可读性非常好的代码。整体而言,Flow是一个重要且值得深入学习的话题,对于想要深入了解Kotlin协程的开发者来说,是一篇不可多得的精华文章。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《朱涛 · Kotlin 编程第一课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(21)

  • 最新
  • 精选
  • Paul Shan
    思考题:flow本身已经提供了线程切换的中间操作符flowOn和launchIn,来确定不同部分的线程边界并优化,withContext要再次切换线程,势必打破flow规划好的线程边界,估计要出错,抛出异常来提前报错。

    作者回复: 没错,是这个意思。

    2022-03-24
    12
  • 白乾涛
    以下代码,为啥没有任何日志输出? 而且,为啥程序不会结束? fun main() = runBlocking { withContext(dispatcher) { flow { log("emit"); emit(1) } .flowOn(Dispatchers.Main) .filter { log("filter"); it > 0 } .collect { log("collect") } } }

    作者回复: 我在第17讲当中提到过,在非UI平台上,Dispatchers.Main是没有意义的。只有在Android、Swing之类的平台,我们才可以使用Dispatchers.Main。

    2022-03-12
    4
  • 曾帅
    关于思考题,翻了一下 emit() 的代码,发现里面有说到这一点。 里面说不允许在 withContext 里 调用 emit() 是因为 emit() 默认不是线程安全的,而且还给出了一种解决方案,那就是使用 channel 来处理。

    作者回复: 能从源码中找到证据,很好。

    2022-03-10
    3
  • Paul Shan
    原来在使用Rxjava还是Coroutine的时候,我还是支持使用Rxjava的,Flow出来之后,我就倒向Coroutine Flow。

    作者回复: 是的,Flow的学习成本会更低一些。

    2022-03-24
    2
  • 魏全运
    Flow 跟RxJava 的使用方式太像了

    作者回复: 思想是类似的,但Flow的操作符比RxJava还是要少很多的。所以会更容易上手一些。

    2022-03-04
    3
    2
  • pengzhaoyang coder
    发送的数据必须来自同一个协程内,不允许来自多个CoroutineContext,所以默认不能在flow{}中创建新协程或通过withContext()切换协程。如需切换上游的CoroutineContext,可以通过flowOn()进行切换

    作者回复: 是的,那么,为什么不允许多个CoroutineContext?

    2022-04-01
  • Paul Shan
    Flow也有热的SharedFlow,还支持一对多的服务,我自己的经验是,Channel在具体场景中基本可以被Flow替代,而且更方便更安全。

    作者回复: 是的,Channel更像是一个底层的基础工具,不太适合直接拿来用。

    2022-03-24
  • 梁中华
    大佬,能结合几个服务端的例子讲讲不,其实协程的主要发挥场景还是在服务端,就像gorouting一样

    作者回复: 感谢你的建议,我会考虑的。

    2022-03-22
  • dawn
    大佬,为什么下面的代码没法结束 fun main() { val asCoroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() val scope = CoroutineScope(asCoroutineDispatcher) runBlocking { flow { emit(1f) emit("s") kotlinx.coroutines.delay(2000) emit(2f) emit(3f) // throw NullPointerException() emit(4f) emit(5f) logX("emit") }.filter { logX("filter") it is Float }.onStart { logX("onStart") }.onCompletion { logX("onCompletion") }.catch { logX("catch $it") }.onEach { logX(it) }.launchIn(scope) logX("end") } }

    作者回复: 其实是因为:你代码中的Dispatcher对应的线程池没有定义成守护线程。 ``` fun main() { // 请留意 Dispatcher 当中的isDaemon = true val mySingleDispatcher = Executors.newSingleThreadExecutor { Thread(it, "MySingleThread").apply { isDaemon = true } }.asCoroutineDispatcher() val scope = CoroutineScope(mySingleDispatcher) runBlocking { flow { emit(1f) emit("s") kotlinx.coroutines.delay(2000) emit(2f) emit(3f) // throw NullPointerException() emit(4f) emit(5f) logX("emit") }.filter { logX("filter") it is Float }.onStart { logX("onStart") }.onCompletion { logX("onCompletion") }.catch { logX("catch $it") }.onEach { logX(it) }.launchIn(scope) logX("end") } } ```

    2022-03-16
    2
  • 白乾涛
    为啥下面代码中的 emit 也执行在 IO 线程? fun main() = runBlocking { val flow = flow { log("emit"); emit(1) } withContext(Dispatchers.IO) { flow.filter { log("filter"); it > 0 } .collect { log("collect") } } }

    作者回复: 这很合理,在默认情况下,emit是会使用collect的协程上下文的。

    2022-03-12
    3
收起评论
显示
设置
留言
21
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部