Kotlin Flow和LiveData的高级协程

1、flow的简介

我们将使用来自kotlin-coroutinesflow构建相同的逻辑。在开始构建之前,我们先了解一下flow是什么,以及如何将flow纳入您的应用中。

flow是序列的一部版本,这是一种收集类型,其中的值是逐个生成的。与序列一样,只有需要某个值时,flow才会根据需要生成该值,而且flow可以包含无限数量的值。

那么,为什么Kotlin要引入新的Flow类型,它与常规序列有什么不同?答案就在于异步性的魔力。Flow全面支持协程。这意味着您可以使用协程构建、转换和耗用Flow。您还可以控制并发,即利用Flow通过声明的方式协调多个协程的执行。

这带来了许多令人兴奋的可能性。

flow是值的异步序列

Flow通过异步操作(例如网络请求、数据库调用或其他异步代码),一次生成一个值(而不是一次生成所有值)。它通过其API支持协程,因此您也可以使用协程来转换flow。

Flow可以在完全响应式风格的编程中使用。如果您之前使用过RxJava这类元素,Flow提供类似的功能。可以使用mapflatMapLatestcombine等函数运算符对flow进行转换,从而简洁明了地表达应用逻辑。

Flow还支持在大多数运算符中调用挂起函数。这样您就可以在map等运算符中执行连续的异步任务。通过在flow中使用挂起操作,通常会产生比等效的完全响应式代码更短、更易读的代码。

1.1、flow如何运行

如需了解如何使用flow根据需要(或逐个)生成值,请查看下面的flow。此flow发出值(1, 2, 3),并在生成每一项之前、期间和之后输出响应结果。

fun makeFlow() = flow {
    println("sending first value")
    emit(1)
    println("first value collected, sending another value")
    emit(2)
    println("second value collected, sending a third value")
    emit(3)
    print("done")
}

scope.launch {
    makeFlow().collect { value ->
        print("got $value")
    }
    println("flow is completed")
}

运行上面的代码会输出以下内容:

sending first value
got 1
first value collected, sending another value
got 2
second value collected, sending a third value
got 3
done
flow is completed

您可以看到collectlambda与flow构建器如何交替执行。每次flow构建器调用emit时,它都会suspends,直到元素完全处理为止。当从flow中请求另一个值时,它会从上次停止的位置resumes,直到它再次调用emit。flow构建器完成后,Flow将被取消,同时collect恢复,从而允许调用协程输出flow is completed

collect的调用非常重要。Flow使用挂起运算符(例如collect),而不是公开Iterator界面,以便始终知晓flow何时在被主动耗用。更重要的是,它可以在调用方无法再请求更多值时获知消息,以便清理资源。

Flow完全是使用协程构建的。通过使用协程的suspend和resume机制,可以将生产方(`flow`)的执行与使用方(collect)同步。

如果您使用过响应式数据流并熟悉backpressure的概念,那么它是通过挂起协程在Flow中实现的。

1.2、flow何时运行

collect运算符运行时,上述示例中的Flow就会开始运行。通过调用flow构建器或其他API来创建新的Flow不会执行其他工作。挂起运算符collectFlow中被称为终端运算符。还有其他挂起终端运算符,例如kotlinx-coroutines附带的toListfirstsingle,您也可以构建您自己的终端运算符。

默认情况下,Flow将在以下情况下执行:

  • 每次应用终端运算符时(且每个新调用均与之前启动的任何调用无关)
  • 直到运行flow的协程被取消
  • 当上一个值已完全处理,并且又请求了另一个值时

这些规则是Flow的默认行为,但我们可以使与先前运行具有相同状态的Flow不会针对每个终端运算符重启,而是通过Flow的内置或自定义转换独立于收集操作执行。

执行Flow的过程称为收集flow。默认情况下,Flow在被收集之前不会执行任何操作,即应用任何终端运算符。

myFlow.toList() // toList collects this flow and adds the values to a List

也可以说是通过终端运算符从Flow中收集了单个值。

myFlow.collect { item -> println("$item has been collected") }

由于这些规则,Flow可以参与结构化并发操作,并且可以安全地从Flow启动长时间运行的协程。Flow不会泄露资源,因为在调用方被取消时,系统始终会按照协程合作取消规则清理这些资源。

下面,我们将使用take运算符修改上面的flow,以便仅查看前两个元素,然后再收集该flow两次。

scope.launch {
    val repeatableFlow = makeFlow().take(2) // we only care about the first two elements
    println("first collection")
    repeatableFlow.collect()
    println("collecting again")
    repeatableFlow.collect()
    println("second collection completed")
}

运行此代码后,您将看到以下输出:

first collection
sending first value
first value collected, sending another value
collecting again
sending first value
first value collected, sending another value
second collection completed

每次调用collect时,flowlambda都会从头开始执行。如果flow执行高成本的工作(例如发出网络请求),则这一点非常重要。此外,由于我们应用了take(2)运算符,因此该flow将只生成两个值。第二次调用emit后,将不会再次恢复flow lambda,因此将不会再输出“second value collected…”这一行。

2、通过flow运行异步

那么, FlowSequence一样是惰性的,但它如何又是异步的呢?我们来看一下异步序列的示例-观察对数据库的更改。

在此示例中,我们需要使用另一个线程(如主线程或界面线程)上的观察器来协调数据库线程池上生成数据。并且,随着数据发生更改,我们将反复发出结果,所以这种情况非常符合异步序列模式。

假设您正在为Flow编写Room集成。如果您从Room中已有的挂起查询支持开始,则可能会编写如下代码:

// This code is a simplified version of how Room implements flow
fun <T> createFlow(query: Query, tables: List<Tables>): Flow<T> = flow {
    val changeTracker = tableChangeTracker(tables)
    
    while(true) {
        emit(suspendQuery(query))
        changeTracker.suspendUntilChanged()
    }
}

此代码依靠两个虚构挂起函数生成Flow:

  • suspendQuery - 该主线程安全函数用于运行常规Room挂起查询
  • suspendUntilChanged - 该函数用于挂起协程,直至其中一个表发生更改。

flow被收集后,最初会emits查询的第一个值。处理该值后,flow将恢复并调用suspendUntilChanged,正如其预期实现的操作一样 - 挂起flow,直至其中一个表发生更改。此时,系统中不会发生任何变化,直至其中一个表发生更改并且flow恢复。

当flow恢复时,系统将执行另一个主线程安全查询并emits结果。这个过程会永远无限循环下去。

2.1、flow和结构化并发

不过,我们不想发成工作泄露。协程本身的资源开销并不高,但它会反复唤醒自身去执行数据库查询。泄露的代价相当高。

虽然我们创建了无限循环,但Flow可以通过支持结构化并发帮助我们解决这个问题。

耗用值或对flow进行迭代的唯一方法就是使用终端运算符。因为所有终端运算符都是挂起函数,因此该工作受限于调用它们的作用域的生命周期。该作用域取消后,flow将按照常规协程合作取消规则自动取消。因此,即使我们在flow构建器中编写了无限循环,由于结构化并发,我们仍然可以安全地耗用flow,不会发生泄露。

flow支持结构化并发

由于flow允许您仅通过终端运算符耗用值,因此它可以支持结构化并发。

当flow的使用方被取消时,整个Flow都会被取消。由于结构化并发,中间步骤不可能泄露协程。

最后

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

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

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

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

图片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值