自Flow首次在Kotlin版本1.3.0中首次引入以来,它已成为Kotlin社区中重要且高度讨论的主题。但是为什么?
流是Kotlin中异步编程的简单但功能强大的实现。
流量不过是返回多个结果的Coroutine。它允许开发人员以顺序样式编写结构并发,异步和非阻滞代码。但是,所有这些技术术语对新开发人员意味着什么,很难一次掌握所有这些技术。我花了一些时间来理解它。
流量可以执行此操作而不会太昂贵的原因是因为它具有非常简单的内部实现。流不过是两个接口及其功能相互呼唤。让我解释一下,但首先,暂时忘记了有关Coroutines暂停功能和所有内容的任何了解。
让我们开始。
要了解内部流程的工作原理,我们只需要了解两个主要概念。
-
接口和接口的匿名实现
-
功能接口或Kotlin中的单个抽象方法(SAM)接口
让我们创建两个流界面。
public interface Flow {
public fun collect(collector: FlowCollector)
}
public interface FlowCollector {
public fun emit(value: String)
}
val outputStream = object : Flow {
override fun collect(collector: FlowCollector) {
collector.emit("first")
collector.emit("second")
}
}
outputStream.collect(
object : FlowCollector {
override fun emit(value: String) {
println(value)
println(value.uppercase())
}
}
)
/*
OUTPUT:
first
FIRST
second
SECOND
*/
P.S: For simplicity, we are currently only considering the Flow type as a string.
就是这样!这是其最简单和最简单的形式的流程,到目前为止,我们仅使用了第一个概念。如您所见,这不过是一个函数直接调用另一个函数多次。
让我们使用Functional Interface
使它变得更漂亮一个只有一种抽象方法的接口称为aâ功能接口或A 单个抽象方法(SAM)接口。功能界面可以具有多个非抽象成员,但只有一个抽象成员。
public interface Flow {
public fun collect(collector: FlowCollector)
}
public fun interface FlowCollector {
public fun emit(value: String)
}
val outputStream = object : Flow {
override fun collect(collector: FlowCollector) {
collector.emit("first")
collector.emit("second")
}
}
outputStream.collect { value ->
println(value)
println(value.uppercase())
}
// SAME OUTPUT
现在看起来已经开始看起来相似了。流程接口的匿名实现是样板代码。可以通过将顶级函数与流匿名实现作为返回类型来解决。
fun flow(block: FlowCollector.() -> Unit): Flow = object : Flow {
override fun collect(collector: FlowCollector) {
collector.block()
}
}
public interface Flow {
public fun collect(collector: FlowCollector)
}
public fun interface FlowCollector {
public fun emit(value: String)
}
val outputStream = flow {
emit("first")
emit("second")
}
outputStream.collect { value ->
println(value)
println(value.uppercase())
}
//SAME OUTPUT
这是我们在引擎盖下使用的熟悉的流程。这似乎并不像我们上面提到的那样丰富。从理论上讲,任何编程语言都可以这样做,那么什么使Kotlin如此独特?答案是Kotlin的魔术功能关键字“ 暂停。
public interface Flow {
public suspend fun collect(collector: FlowCollector)
}
public fun interface FlowCollector {
public suspend fun emit(value: String)
}
悬挂功能意味着它们可以做的不仅仅是简单的功能。使这些功能悬挂可确保只能从其他悬挂功能或Coroutine构建器中调用流量。尝试记住Coroutines可以做什么。流融合了Coroutine的所有功能。例如:
-
结构化并发:流动构建器块(收集函数)默认情况下,与流量收集器块在同一coroutine范围内运行。因此,当范围停止时,建筑商和收集器块都停止执行,从而节省了宝贵的CPU资源。无需手动关闭流。
-
异步,非阻滞代码:我们可以控制哪个代码决定哪个代码应运行哪个调度程序。这使得它对于在背景线程上运行IO操作非常有用,而无需阻止原始/主线程。
-
顺序&可读:悬挂功能等待顺序中其他悬挂功能的结果,直到那时它们一直处于悬浮状态,而无需阻止线程。
-
背压:流动构建器和收集器块在同一coroutine上运行,因此当收集器块正在处理结果时,构建器块被悬挂以防止其产生不必要的结果,而构建器块可能无法处理。默认情况下,此特殊功能带有流程。其他图书馆必须进行精美的解决方法才能实现这一目标。
以及其他类似的功能。
流的旗舰功能之一是其运算符功能。他们很复杂吗?不,因为流不过是简单的功能互相呼叫。因此,如果我们以流量为返回类型创建一个扩展功能,则可以从理论上完成任何事情。让我们看一下onStart
和onCompletion
运算符函数。
fun Flow.onStart(
action: FlowCollector.() -> Unit
) = flow {
action()
collect(this)
}
fun Flow.onCompletion(
action: FlowCollector.() -> Unit
) = flow {
collect(this)
action()
}
......
outputStream
.onStart {
println("onStart")
}
.onCompletion {
println("onComplete")
}
.collect { value ->
println(value)
println(value.uppercase())
}
/*
OUTPUT:
onStart
first
FIRST
second
SECOND
onComplete
*/
PS: This is a highly simplified explanation. There are many complex processes happening behind the scenes, but the main logic remains unchanged.
因此,如您所见,我们有能力创建我们可以想象使用扩展功能的任何类型的流动运算符。随意发疯!
为了满足某人的好奇心,这是一个通用类型的流界面:
public interface Flow<out T> {
public suspend fun collect(collector: FlowCollector<T>)
}
public fun interface FlowCollector<in T> {
public suspend fun emit(value: T)
}
我希望您了解为什么流是一个简单而强大的API实现。
资源:
- kotlinconf 2019:罗马·伊丽莎白(Roman Elizarov)的kotlin流动数据流: https://youtu.be/tYcqn48SMT8?si=4uo-G2Ryw4IQPYdS&t=1385
- 功能(SAM)接口: https://kotlinlang.org/docs/fun-interfaces.html
- kotlin扩展功能: https://kotlinlang.org/docs/extensions.html
- kotlin仿制药: https://kotlinlang.org/docs/generics.html#variance