一句话总结:
冷流是“一次性的数据蓝图”,为每个订阅者独立执行;热流是“共享的数据总线”,在特定生命周期内持续存在。而stateIn和shareIn操作符,则是将数据蓝图“激活”成共享总线的关键“转换器”。
一、冷流 (Cold Flow) - “一次性”的数据请求蓝图
冷流的本质不是数据源,而是一个生产数据的计划书(Blueprint) 。
- 核心特性:只有
collect()被调用时,计划书才会被从头到尾完整执行一遍。 - 最佳实践:非常适合定义在**数据层(Repository)**中,用于封装一次性的数据操作,如网络请求或数据库查询。
class NewsRepository(private val api: NewsApi) {
// 定义一个获取新闻的“计划书”,它本身不执行任何操作
fun fetchLatestNews(): Flow<List<News>> = flow {
// 当有订阅者collect时,这行代码才会被执行
val latestNews = api.getNews()
emit(latestNews)
}
}
二、从“冷”到“热”的转变:共享的艺术 (stateIn & shareIn)
在ViewModel中,我们通常不希望每次UI重绘(如屏幕旋转)都重新执行一遍fetchLatestNews()。我们需要将这个“冷”的计划书,转换成一个“热”的、可共享的、能在ViewModel生命周期内持续存在的数据源。
这就是stateIn和shareIn的用武之地。
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
// 使用stateIn将冷流转换为一个热的StateFlow
val uiState: StateFlow<NewsUiState> = repository.fetchLatestNews()
.map { news -> NewsUiState.Success(news) } // 转换成UI状态
.catch { e -> emit(NewsUiState.Error(e)) } // 处理异常
.stateIn(
scope = viewModelScope, // ① 指定热流的生命周期范围
started = SharingStarted.WhileSubscribed(5000), // ② 指定共享策略
initialValue = NewsUiState.Loading // ③ 提供一个初始值
)
}
关键参数解读:
scope: 热流的“生命线”,通常是viewModelScope,确保ViewModel销毁时,上游的数据流也会被取消。started: 共享的启动和停止策略。SharingStarted.WhileSubscribed(5000)是Google推荐的最佳实践,意味着:当最后一个订阅者消失后,等待5秒,如果还没有新的订阅者,就停止上游流以节省资源。initialValue/replay: 热流的“记忆”。stateIn需要一个初始值,而shareIn可以配置replay数量,让新订阅者收到最近的N条历史数据。
三、热流家族的“双子星”:StateFlow 与 SharedFlow
通过stateIn和shareIn转换后,我们就得到了两种定位不同的热流。
StateFlow - 状态的“唯一真实来源”
-
比喻:会议室里的一块白板。它永远展示着当前的最新内容。
-
特性:
- 永远有值:创建时必须有初始值。
- 值是唯一的:不会发射与前一个值相同的数据。
- 值会被合并(Conflation) :如果生产者更新状态过快,消费者只会收到最新的值。
-
用途:UI状态管理。它是Android平台上
LiveData的现代替代品。
SharedFlow - 事件的“广播总线”
-
比喻:一个扩音喇叭。它喊出的话(事件),只有当时在场的人才能听到。
-
特性:
- 可以没有初始值。
- 可以配置重播(replay) :让新来的听众也能听到之前的几句话。
- 可以配置缓冲策略:当喊话太快听众处理不过来时,是丢掉旧的、新的,还是挂起等待。
-
用途:**一次性事件(Single-shot events)**的分发,如显示一个Toast、导航、弹出一个Dialog。
四、总结与架构选型
| 特性 | 冷流 (flow) | 热流 (StateFlow) | 热流 (SharedFlow) |
|---|---|---|---|
| 本质 | 数据生产蓝图 | 共享的状态容器 | 共享的事件总线 |
| 生命周期 | 依赖于collect | 依赖于指定的scope | 依赖于指定的scope |
| 典型位置 | Repository (数据层) | ViewModel (UI逻辑层) | ViewModel (UI逻辑层) |
| 转换方式 | N/A | stateIn | shareIn |
| 核心场景 | 定义一次性数据操作 | 向UI暴露可观察的状态 | 向UI分发一次性事件 |
结论:
在现代Android开发中,我们通常遵循一个标准模式:在数据层使用冷流(Flow)作为数据源的“蓝图”,然后在ViewModel中使用stateIn或shareIn,将其转换为与UI生命周期绑定的、可共享的、健壮的热流(StateFlow或SharedFlow),最终由UI层安全地进行订阅和消费。 理解这个从冷到热的转换流程,是掌握Kotlin Flow在架构中应用的关键。