Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(下)

简介: Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(下)

4. StateFlow 代码实战


说了这么多 Flow 的东西,最后以一个实际的例子结束这一章节的学习笔记吧!

下面我将用一个应用实例来讲解 StateFlow 的实际应用。这个例子将会用到 debouncedistinctUnitChangedflatMapLatest 等操作符,用这些操作符去实现一个文本输入中实时查询的例子。

假设有个需求,要实现一个浏览器搜索的功能,根据用户不断输入的字符去查询相关的内容。如果不做任何处理,用户对键入的字符串做的任何修改,都会去请求一次接口,那后端服务器肯定是吃不消的;对于用户而言,在不断输入的过程中返回的结果用户并不会很关心,他只会关心最终输入完成之后请求的数据。那么,如何减少后端的接口请求次数是关键所在。

先来看看核心的代码:

// code 8   ViewModel.kt 文件
val queryStateFlow = MutableStateFlow("")
fun getQueryResult(): Flow<String> {
    return queryStateFlow
        .debounce(300L)
        .distinctUntilChanged()
        .flatMapLatest {
            if (it.isNullOrBlank()) {
                flow { emit("") }
            } else {
                dataFromNetwork(it).catch {
                    emitAll( flow { emit("") } )
                }
            }
        }
        .flowOn(Dispatchers.IO)
}
// 模拟网络请求的耗时操作
private fun dataFromNetwork(query: String): Flow<String> {
    return flow {
        delay(2000)
        emit(query) // 返回请求的结果
    }
}

首先可以直观地感受到,使用 Flow 去处理这一逻辑较为简单,代码量较少,这也是 Flow 的魅力所在。我们按顺序介绍一下所使用到的 Flow 操作符:

debounce 操作符

具体的操作符方法声明:

// code 9
public fun <T> Flow<T>.debounce(timeoutMillis: Long): Flow<T>

用于过滤掉最新的发射值之前 timeoutMillis 时间内发射的值,返回一个过滤后的 Flow。官方栗子非常清楚:

// code 10
flow {
    emit(1)
    delay(90)
    emit(2)
    delay(90)
    emit(3)
    delay(1010)
    emit(4)
    delay(1010)
    emit(5)
}.debounce(1000)
最终会发射出下面的三个值:
3, 4, 5

发射 1 之后不到 1000ms 又发射了 2,所以 1 就会被过滤掉不会发射了,以此类推。所以最后发射的值是一定可以发射成功的。通过这个操作符,我们就可以有效减少频繁请求接口的问题,这里设置的 timeout 为 300ms,即在用户连续输入过程中每间隔 300ms 才去请求一次数据。

distinctUntilChanged 操作符

具体操作符声明为:

// code 11
public fun <T> Flow<T>.distinctUntilChanged(): Flow<T>

用于过滤掉重复的发射值。虽然 StateFlow 本身就可过滤掉没有变化的发射值,但在这里还是需要的,因为用户可能会删除刚输入的字符,这一操作符可进一步减少不必要的接口请求。

flatMapLatest 操作符

我看的代码版本这个操作符还是实验性api,后续可能被移除。具体操作符声明为:

// code 12
@ExperimentalCoroutinesApi
public inline fun <T, R> Flow<T>.flatMapLatest(@BuilderInference crossinline transform: suspend (value: T) -> Flow<R>): Flow<R>

这个操作符可以在原流的基础上生成一个新流,当原流依次发出 a、b 两值时,新流都会接收,但如果新流 a 值的相关操作还未结束,则会取消 a 值的相关操作,并用 b 值进行操作。简单说就是,丢弃旧值操作,换用新值操作。下面是一个例子:

// code 13
    fun flatMapLatestDemo() {
        val testFlow = flow {
            emit("a")
            delay(100)
            emit("b")
        }.flatMapLatest {
            flow {
                emit("receive $it")
                delay(200)
                emit("send $it")
            }
        }
        lifecycleScope.launch {
            testFlow.collect {
                println("----$it")
            }
        }
    }

image.png

通过打印的 log 可以看出,a,b 都被 flatMapLatest 操作符接收到了,只有 b 最终通过。这是因为 a 先到达,等待了 100ms 后新的值 b 也到了,但 a 还在等待中,这时 flatMapLatest 就会取消掉 a 后续的操作。如果把 delay(200) 改成 delay(50),那最终 a,b 都能被打印出来。

所以这个操作符在 code 8 中的作用就是进一步减少接口请求的次数。当输入的新字符串到来时,就会将之前旧字符串还未结束的请求操作取消掉,用新的字符串去请求数据。

ViewModel.kt 的代码终于说完了,其他的代码就比较常规了,直接上码:

// code 14  MainActivity.kt
binding.editText.addTextChangedListener(object : TextWatcher{
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
    override fun onTextChanged(input: CharSequence?, start: Int, before: Int, count: Int) {
        viewModel.queryStateFlow.value = input.toString()
    }
    override fun afterTextChanged(s: Editable?) { }
})
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.getQueryResult()
            .collect {
                binding.tvText.text = it
            }
    }
}

有关 Flow 的相关知识就到此结束了,来个简单总结吧~


总结


1)shareInstateIn 都可将冷流转化为热流,将数据共享给多个消费者,无需为每个消费者创建同一个数据流的新实例。两者通常用于提升性能,在没有消费者时缓存数据;

2)SharingStarted 启动方式有 EagerlyLazilyWhileSubscribed 三种,最常用的还是 WhileSubscribed,有消费者就启动,没有就停止,还能设置停止延时时长和缓存过期时长;

3)注意 shareInstateIn 都会新建一个 Flow,不要用于方法的返回值,建议赋值给属性;

4)shareInstateInonStartonCompletion 等搭配可监听转成的热流的状态;

5)distinctUntilChanged 操作符可过滤重复数据,一般用于 SharedFlow;debounce 可用于在某一时间段内防抖;flatMapLatest 操作符可以用最新值替换旧值发送给下游,旧值直接被取消作废。

更多内容,欢迎关注公众号:修之竹 或者查看 修之竹的 Android 专辑

赞人玫瑰,手留余香!欢迎点赞、转发~ 转发请注明出处~


参考文献


  1. StateFlow 和 SharedFlow 官方文档  https://developer.android.google.cn/kotlin/flow/stateflow-and-sharedflow?hl=zh-cn
  2. Flow 操作符 shareIn 和 stateIn 使用须知;Android开发者;https://mp.weixin.qq.com/s/PbqF-vzDrttYq-cSR6NDmQ
  3. Kotlin协程:冷流转换热流的使用与原理;LeeDuo;https://blog.csdn.net/LeeDuoZuiShuai/article/details/127145092
目录
相关文章
|
2月前
|
移动开发 安全 Android开发
构建高效Android应用:Kotlin协程的实践与优化策略
【5月更文挑战第30天】 在移动开发领域,性能优化始终是关键议题之一。特别是对于Android开发者来说,如何在保证应用流畅性的同时,提升代码的执行效率,已成为不断探索的主题。近年来,Kotlin语言凭借其简洁、安全和实用的特性,在Android开发中得到了广泛的应用。其中,Kotlin协程作为一种新的并发处理机制,为编写异步、非阻塞性的代码提供了强大工具。本文将深入探讨Kotlin协程在Android开发中的应用实践,以及如何通过协程优化应用性能,帮助开发者构建更高效的Android应用。
|
14天前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
2天前
|
安全 Kotlin
Kotlin中的安全导航操作符?.、空合并运算符?:以及let函数的实践与理解
Kotlin中的安全导航操作符?.、空合并运算符?:以及let函数的实践与理解
3 0
|
2月前
|
API 调度 Android开发
打造高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第27天】在移动开发领域,性能优化和响应速度是衡量应用质量的关键因素。随着Kotlin语言的普及,协程作为其核心特性之一,为Android开发者提供了一种全新的并发处理方式。本文深入探讨了Kotlin协程在Android应用开发中的优势,并通过实例演示如何在实际项目中有效利用协程提升应用性能和用户体验。
|
2月前
|
移动开发 Android开发 开发者
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第21天】在移动开发领域,性能优化和流畅的用户体验是至关重要的。随着Kotlin语言在Android平台的广泛采纳,其并发处理的强大工具—协程(Coroutines),已成为提升应用响应性和效率的关键因素。本文将深入分析Kotlin协程的核心原理,探讨其在Android开发中的优势,并通过实例演示如何有效利用协程来优化应用性能,打造更加流畅的用户体验。
30 4
|
2月前
|
物联网 区块链 Android开发
构建高效Android应用:Kotlin与Jetpack的实践之路未来技术的融合潮流:区块链、物联网与虚拟现实的交汇点
【5月更文挑战第30天】 在移动开发领域,效率和性能始终是开发者追求的核心。随着技术的不断进步,Kotlin语言以其简洁性和现代化特性成为Android开发的新宠。与此同时,Jetpack组件为应用开发提供了一套经过实践检验的库、工具和指南,旨在简化复杂任务并帮助提高应用质量。本文将深入探索如何通过Kotlin结合Jetpack组件来构建一个既高效又稳定的Android应用,并分享在此过程中的最佳实践和常见陷阱。
|
2月前
|
运维 监控 Android开发
构建高效自动化运维系统的策略与实践构建高效Android应用:Kotlin协程的实践指南
【5月更文挑战第29天】随着信息技术的迅猛发展,企业IT基础设施变得日益复杂,传统的手动运维模式已难以满足高效率、高稳定性的要求。本文将深入探讨如何通过自动化工具和策略来构建一个高效的自动化运维系统。文中不仅分析了自动化运维的必要性,还详细介绍了实现过程中的关键步骤,包括监控、配置管理、故障响应等,并结合实际案例分析其效果,以期为读者提供一套行之有效的自动化运维解决方案。
|
2月前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第29天】 随着移动开发技术的不断进步,开发者寻求更高效、更简洁的方式来编写代码。在Android平台上,Kotlin语言凭借其现代化的特性和对协程的原生支持,成为提高开发效率的关键。本文将深入分析Kotlin协程的核心优势,并通过实例展示如何在Android应用开发中有效地利用协程来处理异步任务,优化性能,以及提升用户体验。通过对比传统线程和回调机制,我们将揭示协程如何简化异步编程模型,并减少内存消耗,使应用更加健壮和可维护。
|
2月前
|
移动开发 安全 编译器
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第27天】 在移动开发领域,性能优化和流畅的用户体验始终是开发者追求的目标。随着Android对Kotlin的支持日益增强,Kotlin协程作为一种新的并发处理方式,为Android应用的性能提升提供了新的可能性。本文将深入探讨Kotlin协程的核心优势,并通过具体实例展示如何在Android应用中有效利用协程来提升响应速度、减少内存消耗,并简化异步代码。
|
2月前
|
存储 缓存 算法
深入理解操作系统内存管理:分页系统的优势与挑战构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第27天】 在现代计算机系统中,内存管理是操作系统的核心功能之一。分页系统作为一种内存管理技术,通过将物理内存划分为固定大小的单元——页面,为每个运行的程序提供独立的虚拟地址空间。这种机制不仅提高了内存的使用效率,还为多任务环境提供了必要的隔离性。然而,分页系统的实现也带来了一系列的挑战,包括页面置换算法的选择、内存抖动问题以及TLB(Translation Lookaside Buffer)的管理等。本文旨在探讨分页系统的原理、优势及其面临的挑战,并通过分析现有解决方案,提出可能的改进措施。