前语

如今android开发基本上从之前的Java言语转而运用Kotlin言语,MMVM模式中用于保存UI状况的东西LiveData也逐渐被Flow代替。下面将逐渐介绍Kotlin的Flow相关知识,以及怎么与Coroutine配合运用,写出漂亮的声明式,呼应式代码,当然最重要的是功用强壮,可读性强,易于保护!

Flow介绍

Flow 是 Kotlin 协程库中的一个概念和类,用于处理异步数据流。它供给了一种声明式的办法来处理连续的、异步的数据序列,而且与协程无缝集成。

以下是 Flow 的一些关键特性和优势

  1. 异步数据流:Flow 答应以异步的办法处理连续的数据流。它能够处理很多的数据或长时间运转的操作,而无需阻塞主线程
  2. 声明式编程:Flow 供给了一种声明式的编程模型,经过操作符(operators)链式调用来处理数据流。这使得代码更简练、易读和易于保护。
  3. 可组合性:Flow 的操作符能够组合在一同,构建杂乱的数据转换和处理逻辑。您能够运用 mapfilterflatMapzip 等操作符来转换、过滤、合并和组合数据流。
  4. 挂起函数:Flow 的操作能够在挂起函数中履行,使其适用于与协程一同运用。这样能够方便地进行异步操作和并发编程,避免了回调阴间和杂乱的线程办理。
  5. 撤销支撑:Flow 具有与协程相同的撤销支撑。能够运用 cancelcollect 中的 cancellable 参数或 withTimeout 等函数来撤销数据流的搜集和处理。

在介绍flow详细用法之前,先说明下flow的一些概念:

Flow组成

  • Producers(生产者):数据流的产生emit
  • Customers(顾客):数据流的搜集collect
  • Operators(中心操作符):数据流的二次加工

flow的冷流&暖流

在 Kotlin 的协程中,”冷流”(Cold Flow)和”暖流”(Hot Flow)是用来描述 Flow 和 SharedFlow 两种不同的数据流的特性,还有一种特别的暖流,StateFlow,它承继自SharedFlow

public interface StateFlow<out T> : SharedFlow<T> {
    /**
     * The current value of this state flow.
     */
    public val value: T
}

cold flow & hot flow差异

  1. 冷流(Cold Flow):

    • 冷流是指每次订阅都会从头开端并独立运转的数据流。
    • 当每个订阅者开端搜集数据时,冷流会从头开端发射数据,每个订阅者都会独立地接收到完整的数据流。
    • 例如,经过调用 Flow 的 collectcollectLatest 函数,能够订阅冷流并搜集数据。
  2. 暖流(Hot Flow):

    • 暖流是指现已开端发射数据并在订阅之前运转的数据流。
    • 暖流在启动时就开端发射数据,不管是否有订阅者。
    • 假如订阅者在流现已开端发射数据后加入,它们可能会错过一些数据。
    • 例如,经过调用 SharedFlow 的 asSharedFlow 函数,能够创立暖流,并能够经过 collect 函数订阅。

Flow运用

class SecondFragment : Fragment() {
    //……省掉无关代码
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.buttonFlow.setOnClickListener {
            lifecycleScope.launch {
                val value = createFlow().first()
                Log.d("flow", "flow.first() = $value")
                val acc = createFlow().fold(0) { acc, item ->
                    acc + item
                }
                Log.d("flow", "flow.fold() = $acc")
                try {
                    val value = createFlow().single()
                    Log.d("flow", "flow.single() = $value")
                } catch (e: Exception) {
                    Log.d("flow", e.toString())
                }
            }
        }
        binding.collectLastBtn.setOnClickListener {
            lifecycleScope.launch {
                createFlow().collectLatest { value ->
                    println("Collecting $value")
                    delay(1000) // Emulate work
                    println("$value collected")
                }
            }
        }
    }
    private fun createFlow(): Flow<Int> {
        return flow {
            emit(100)
            delay(500)
            emit(200)
            emit(300)
        }
    }
}

flow创立

创立一个普通flow很简略,直接如上所述办法createFlow(),直接调用flow{},代码块中运用emit(value)发射数据;别的还有一些其他办法创立flow,例如T.asFlow()flowOf(value: T)等办法,本质都是调用了flow{},详细运用细节看后续Demo;

public fun <T> Iterable<T>.asFlow(): Flow<T> = flow {
    forEach { value ->
        emit(value)
    }
}

flow的常用操作符

first

顾名思义获取到flow数据流中的第一个元素,与之对应的是last()

fold

这个办法源码如下:需求一个参数初始值,用于后续(acc: R, value: T) -> R函数的入参acc,经过collect得到flow发射的每一个值,调用operation,返回终究得到的计算成果;

public suspend inline fun <T, R> Flow<T>.fold(
    initial: R,
    crossinline operation: suspend (acc: R, value: T) -> R
): R {
    var accumulator = initial
    collect { value ->
        accumulator = operation(accumulator, value)
    }
    return accumulator
}

例如:得到的计算成果就是100+200+300 = 600,终究打印flow.fold() = 600

val acc = createFlow().fold(0) { acc, item ->
                    acc + item
                }
Log.d("flow", "flow.fold() = $acc")

single

上述例子中有这样一段code:

try {
    val value = createFlow().single()
    Log.d("flow", "flow.single() = $value")
} catch (e: Exception) {
    Log.d("flow", e.toString())
}

这儿的single()操作符效果如下:

  1. 获取单个元素single() 操作符用于获取 Flow 中的单个元素。假如 Flow 中只包括一个元素,它将返回该元素;假如 Flow 中包括多个元素或没有元素,它将抛出 IllegalArgumentException 异常。
  2. 用于保证 Flow 只包括一个元素single() 能够用作 Flow 的查看机制,保证 Flow 中只包括一个元素。假如 Flow 中的元素数量不符合预期,single() 将抛出异常,供给了一种简略的验证和安全性查看。
  3. 简化处理单个元素的状况:当你只关心 Flow 中的单个元素,并期望在处理该元素时终止流的搜集时,能够运用 single()。它能够简化对单个元素的处理逻辑。

distinctUntilChanged

数据去重

createFlow().distinctUntilChanged().collectLatest {
    println("emit value = $it")
}

StateFlow

创立

stateFlow初始化的时分必须要有一个初始值

public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)

用法也很简略,几乎和LiveData相同,都有一个value属性,赋值都是给value赋值

private val _stateFlow = MutableStateFlow("Hello world")
val stateFlow: StateFlow<String> = _stateFlow.asStateFlow()
fun triggerStateFlow() {
    _stateFlow.value = "StateFlow"
}

运用

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        launch {
            viewModel.stateFlow.collectLatest {
                binding.stateText.text = it
                Snackbar.make(
                    binding.root,
                    it,
                    Snackbar.LENGTH_LONG
                ).show()
            }
        }
     }
 }

每次给stateflow.value赋值,都会触发collect办法,相似livedata.observe(this), 只不过collect是协程挂起函数,需求在Coroutine.Scope中履行代码块。

怎么与LifecycleScope协作

Flow 介绍使用

这儿Lifecycle.repeatOnLifecycle用法如上图所示,趁便说下该repeatOnLifecycle是根据lifecycte-runtime-ktx:2.4.0版别才有的新接口,假如你的没有找到该api,请查看你的库版别。

要解说这儿为什么要这么运用需求了解activity/fragment生命周期

当咱们直接运用:

lifecycleScope.launch {
    viewModel.triggerFlow().collectLatest {
        binding.flowText.text = it
    }
}

这种办法是不安全的,当app进入后台的时分,生命周期函数是走到onStop,但是此刻flow所在的协程还是处在活泼状况,能够正常搜集数据,这就造成了数据的糟蹋,乃至产生内存走漏现象如下图所示;

Flow 介绍使用

当咱们运用repeatOnLifecycle(Lifecycle.State.STARTED)的时分,看下图:

Flow 介绍使用

当app进入后台的时分咱们的协程挂起函数会处于挂起状况,此刻会停止搜集flow;从头进入前台后,挂起函数会从头运转;

数据防抖动

StateFlowLiveData一个重要的差异在于,LiveData在重复设置value为相同值的状况下,会重复触发observe回调, 它是不防抖的;

StateFlow 防抖,它天生有去重的功用!效果相似Flow.distinctUntilChanged()这是由于它的源码中有这段逻辑:

private fun updateState(expectedState: Any?, newState: Any): Boolean {
    var curSequence = 0
    var curSlots: Array<StateFlowSlot?>? = this.slots // benign race, we will not use it
    synchronized(this) {
        val oldState = _state.value
        if (expectedState != null && oldState != expectedState) return false // CAS support
        if (oldState == newState) return true

stateflow调用distinctUntilChanged会报错如下

@Deprecated(
    level = DeprecationLevel.ERROR,
    message = "Applying 'distinctUntilChanged' to StateFlow has no effect. See the StateFlow documentation on Operator Fusion.",
    replaceWith = ReplaceWith("this")
)
public fun <T> StateFlow<T>.distinctUntilChanged(): Flow<T> = noImpl()

粘性数据(数据倒灌)

当屏幕翻转或跳转返回,或者弹Dialog的时分,stateFlow会产生数据倒灌,stateflow的value会从头发送给顾客,触发collect代码块; 这与LiveData是共同的,后边ShareFlow会讲到怎么避免这种状况!

SharedFlow

创立

SharedFlow和StateFlow相同,SharedFlow也有两个版别:SharedFlowMutableSharedFlow

private val _sharedFlow = MutableSharedFlow<String>()
val sharedFlow = _sharedFlow.asSharedFlow()
fun triggerSharedFlow() {
    viewModelScope.launch {
        _sharedFlow.emit("SharedFlow")
    }
}

初始化办法:

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

它和StateFlow差异在于

  1. 没有初始值;
  2. SharedFlow能够保存历史数据,stateFlow只会保存最新的值;
  3. SharedFlow发射数据用emit,没有setValue办法;

stateFlow承继自SharedFlow

StateFlow是SharedFlow的一种特殊用途、高功用且高效的完成,用于狭隘但广泛运用的同享状况的状况。有关适用于一切同享流的基本规则、约束和运算符,请参阅SharedFlow文档。

StateFlow一直有一个初始值,向新订阅者重播一个最新值,不再缓冲任何其他值,但保存最后发出的值,而且不支撑ResetReplayCache。当运用以下参数创立StateFlow并对其运用distinctUntilChanged运算符时,StateFlow的行为与同享流相同:

// MutableStateFlow(initialValue) is a shared flow with the following parameters:
val shared = MutableSharedFlow(
    replay = 1,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)
shared.tryEmit(initialValue) // emit the initial value
val state = shared.distinctUntilChanged() // get StateFlow-like behavior

当您需求对StateFlow的行为进行调整(例如额定缓冲、重播更多值或省掉初始值)时,请运用SharedFlow 。

运用

运用和StateFlow 相似


lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        launch {
            viewModel.sharedFlow.collectLatest {
                binding.shareText.text = it
            }
        }
    }
}

默许状况下,replay = 0,当有新的订阅者的时分,SharedFlow不会向它发送数据。这儿有点像通知的感觉。

正由于默许状况下 replay = 0,SharedFlow不会有数据倒灌的状况产生。 详细查看Demo演示。

总结

Flow 供给了一种简练、强壮且可组合的办法来处理异步数据流。它能够与 Kotlin 协程一同运用,为异步编程供给了更优雅的解决方案,并供给了更好的可读性和保护性。Flow 的规划使得处理数据流变得愈加直观和简略,一起具有高效和可扩展的特性。

Flow、StateFlow和SharedFlow是Kotlin协程库中用于处理异步数据流的不同类型。它们适用于不同的运用场景:

  1. Flow:

    • Flow适用于一次性的、连续的异步数据流。
    • 运用Flow能够处理潜在的无限数据流,并在每次订阅时从头开端。
    • Flow是冷流,每个订阅者都会独立地接收到完整的数据流。
    • 合适处理单个值、调集、网络请求、数据库查询等异步操作的成果。
    • 操作符链式调用的声明式编程风格使代码易于理解和组合。
  2. StateFlow:

    • StateFlow适用于具有状况的异步数据流。
    • 它是SharedFlow的一个特化版别,用于表明具有可变状况的数据流。
    • StateFlow保护当时的状况值,并将状况变化经过Flow的办法进行广播。
    • 合适在UI层面中运用,能够完成简略的状况办理,例如表明UI组件的可见性、文本内容等。
  3. SharedFlow:

    • SharedFlow适用于多个订阅者同享的异步数据流。
    • 它是一种暖流,即在开端发射数据后,不管是否有订阅者,都会继续发射数据。
    • SharedFlow答应多个订阅者一起收到相同的数据流,而不是每个订阅者都从头开端数据流。
    • 合适完成事件总线、实时更新、广播消息等场景,能够让多个订阅者调查和呼应相同的数据。
  4. StateFlow在遇到数据倒灌的状况下,数据倒灌不是问题,在某些场景下咱们不需求数据倒灌,能够采用SharedFlow代替;

根据您的运用需求,能够挑选合适的数据流类型。假如只需求一次性的连续数据流,能够运用Flow。假如需求具有可变状况的数据流,能够运用StateFlow。假如需求多个订阅者同享相同的数据流,能够运用SharedFlow。

注意,Flow、StateFlow和SharedFlow都需求在协程效果域内进行搜集和处理,以保证正确的协程上下文和撤销支撑。

参阅

官方文档StateFlow&SharedFlow