Kotlin Flow分类

一、协程官方4大类

从冷热属性划分:普通Flow(冷流)、StateFlow、SharedFlow、callbackFlow / channelFlow,后两者属于特殊构建方式。

  1. Cold‑Flow(冷流):flow{}flowOfasFlow()
  2. Hot‑Flow(热流):StateFlowSharedFlow
  3. 特殊构建:callbackFlowchannelFlow(适配Java回调转为Flow)

1. Cold Flow(普通Flow,冷流)

核心特点

  1. 只有调用collect收集时,flow{}内部代码才会执行;没人订阅不会运行
  2. 每一次collect都会重新执行上游逻辑,多个收集者互相独立,上游会重复执行
  3. 协程取消时流自动停止;完全遵循结构化并发。
  4. 没有初始值,没有缓存,不保存发射过的数据。

创建方式

kotlin 复制代码
//方式1 flow构造器
fun getDataFlow(): Flow<String> = flow {
    println("执行网络请求")
    val result = api.request()
    emit(result)
}
//方式2
flowOf(1,2,3)
listOf(1,2,3).asFlow()

Android适用场景

  1. 单次网络请求、Room数据库监听(Room返回Flow本质也是冷流);
  2. 分页加载、下拉刷新;每次订阅重新请求数据;

缺点:多个页面同时collect会多次发起网络请求。

常用中间操作符

map、filter、transform、onStart、onCompletion、catch、debounce、distinctUntilChanged。

三个flatMap重点(面试高频)
  1. flatMapConcat:串行执行,前一个flow执行完毕才执行下一个,适合顺序请求;
  2. flatMapMerge:并发执行,最多16个并发,适合并行请求;
  3. flatMapLatest:收到新值立刻取消上一个flow,搜索场景(输入新文字取消上一次网络请求)。

2. StateFlow(热流,继承自SharedFlow)

核心特性

  1. 热流:不管有没有collect,都一直在运行;多个收集者共享同一个数据源,上游只执行一次
  2. 强制要有初始值,内部永久保存最新值;新的订阅者进来立刻收到最新值(粘性)
  3. 如果新emit的值和旧值一样会自动过滤,不会重复分发。
  4. 只能通过value赋值更新数据;只读暴露给外部。
kotlin 复制代码
//ViewModel标准写法
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
//更新状态
_uiState.value = UiState.Success(data)

底层原理

MutableStateFlow本质就是 MutableSharedFlow(replay = 1, extraBufferCapacity = 0),默认只缓存最后一条数据。

使用场景(MVVM‑MVI必备)

  1. 页面UI状态:Loading、Success、Error;
  2. 全局登录状态、用户信息、配置信息;
  3. 替代LiveData,不受Android组件生命周期限制;
  4. 多个Fragment共享同一份数据。

注意:StateFlow适合状态,不适合一次性事件(弹窗、toast、页面跳转),会出现事件重复消费问题。

冷流转成StateFlow:stateIn()

把普通冷流转换成热流,避免多次collect重复请求网络:

kotlin 复制代码
val hotFlow = flow { emit(loadData()) }
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = UiState.Loading
    )
  • WhileSubscribed(5000):所有收集者取消之后再保留5秒,短时间内重新订阅不会再次请求网络。
  • SharingStarted.Eagerly:立即启动;Lazily第一个订阅者到来才启动。

3. SharedFlow(通用热流)

核心特性

  1. 热流,支持多订阅者广播;
  2. 默认非粘性事件:新订阅者只能收到订阅之后emit的数据,收不到之前发送的值;
  3. 可以手动配置:replay(重放条数)extraBufferCapacity(缓冲区)
  4. 不会自动过滤重复值,调用emit就发送;
  5. 适合一次性事件:弹窗、Toast、页面跳转、全局事件总线。
kotlin 复制代码
//全局事件总线示例
private val _eventFlow = MutableSharedFlow<AppEvent>(
    replay = 0,        //默认不回放历史消息
    extraBufferCapacity = 5 //缓冲区满时emit会挂起
)
val eventFlow = _eventFlow.asSharedFlow()
//发送事件
viewModelScope.launch {
    _eventFlow.emit(AppEvent.Toast("登录成功"))
}
  • replay = 1:新订阅者可以收到最近一条历史事件(变成粘性);
  • 当缓冲区满,emit会suspend挂起;如果不想挂起可以用tryEmit()

shareIn(冷流转SharedFlow)

kotlin 复制代码
val sharedFlow = flow {}.shareIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(),
    replay = 0
)

StateFlow vs SharedFlow 选型(记住这个结论)

  1. StateFlow:存状态,UI数据,必须初始值,自动去重;replay固定为1
  2. SharedFlow:一次性事件、事件总线;可自由配置回放条数,不会自动去重

4. channelFlow & callbackFlow(适配Java回调转为Flow)

callbackFlow

专门用来把Java回调接口转换成协程Flow(RxJava、OkHttp回调、蓝牙、传感器、WebView回调)。

  • 内部基于Channel实现,支持背压;
  • 必须调用close()或者channel.close()结束流;
  • awaitClose等待回调销毁,防止内存泄漏。

示例:把传统Java回调转为Flow

kotlin 复制代码
fun startSearch(): Flow<String> = callbackFlow {
    val listener = object : SearchListener {
        override fun onResult(result: String) {
            trySend(result)
        }
    }
    searchEngine.setListener(listener)
    awaitClose {
        //页面销毁时移除监听,避免内存泄漏
        searchEngine.removeListener(listener)
    }
}

channelFlow

和callbackFlow底层一致;适合多线程并发emit数据,支持在不同Dispatcher里面发送数据。

kotlin 复制代码
fun produceData():Flow<Int> = channelFlow {
    launch(Dispatchers.IO) { send(1) }
    launch(Dispatchers.Default) { send(2) }
}

注意:普通flow{}内部不允许切换Dispatcher发送emit,channelFlow可以跨线程发送数据。

二、冷热流核心区别总结(面试必背)

类型 冷热属性 初始值 多收集者 新订阅者 典型场景
flow{} 冷流 无初始值 各自独立执行上游 重新执行上游逻辑 网络请求、数据库查询
StateFlow 热流 必须提供初始值 共享同一个上游 立刻收到最新值 UI状态、用户信息
SharedFlow 热流 可选 共享同一个上游 默认收不到历史事件,可配置replay 事件分发、弹窗、跳转
callbackFlow 冷流(按需启动) 无初始值 共享 按需接收 Java回调转Flow

三、Flow高级操作符(Android项目高频)

1. 超时与异常处理

  • catch{}捕获上游异常,只能捕获flow内部异常;
  • onCompletion:流结束(正常结束或异常都会回调);
  • withTimeout(10000):协程超时取消。

2. 防抖、去重

  • debounce(300):300ms内多次发射只取最后一次(搜索输入框);
  • distinctUntilChanged():前后值相同就丢弃。

3. 组合多个Flow

  1. zip:两个flow一一配对,两个流都发出数据后才执行;
  2. combine:任意一个flow更新都会触发回调(全局多个状态合并);
kotlin 复制代码
combine(loginState, configState) { login, config ->
    //任意一个状态变化都会执行
}.collect{}

4. 生命周期感知(配合lifecycleScope)

kotlin 复制代码
viewModelScope.launch {
    uiState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
        .collect {
            //页面STARTED状态才接收数据,页面停止自动取消收集,节省性能
        }
}

四、Android项目落地最佳实践(严格遵守)

  1. 页面单次请求、数据库查询:使用普通Flow ;如果多页面同时观察,用stateIn转成StateFlow避免重复请求。
  2. UI页面状态:MVI模式用StateFlow + sealed‑class UiState
  3. 全局事件(Toast、页面跳转):SharedFlow,replay=0,防止事件重复消费;
  4. Java回调、蓝牙、传感器、第三方SDK回调:统一使用callbackFlow封装
  5. 页面观察Flow全部使用flowWithLifecycle,页面不可见时停止接收数据,减少耗电;
  6. 禁止在ViewModel中使用GlobalScope;全部用viewModelScope、lifecycleScope;
  7. 全局状态管理:Hilt注入单例类,内部持有MutableStateFlow,用Mutex做并发修改保护。

五、协程Flow的异常规则

  1. flow{}内部抛出异常会终止整个流;catch可以捕获异常并恢复流;
  2. StateFlow、SharedFlow本身不会因为emit时异常被取消;
  3. collect的协程被取消,Flow停止接收数据;
  4. 注意:flatMapLatest会自动取消旧协程,不会抛出CancellationException。
相关推荐
并不喜欢吃鱼1 小时前
从零开始 C++----- 十五.一文吃透 C++ 异常:try-catch、栈展开、自定义异常、异常安全 noexcept 底层全剖析
java·开发语言·jvm
gr95ZS6E61 小时前
基础入门java安全(一)--CC1基础分析
开发语言·python
北京阿法龙科技有限公司2 小时前
AR智能眼镜安防应用核心指标:识别距离筑牢防线
java·开发语言·ar
杜子不疼.2 小时前
【C++】继承—C++的秘密武器,get父类的智慧
开发语言·c++
阿pin2 小时前
Android随笔-MVC/MVP/MVVM/MVI
android·mvc·mvvm·mvp·mvi
阿pin2 小时前
Android随笔-View绘制流程
android·开发语言·view
2501_916008892 小时前
iOS 证书管理最佳实践 从创建到续期的完整指南
android·ios·小程序·https·uni-app·iphone·webview
没钥匙的锁12 小时前
04-Java面向对象基础
java·开发语言·mongodb
敖行客 Allthinker3 小时前
IM 融合专题:后端架构师的核心修炼
java·开发语言·数据库