Kotlin shareIn 和 stateIn 使用场景

Kotlin 协程 Flow 的核心源码文件。这个文件主要包含 shareInstateIn 两个强大的操作符,用于将冷流(Cold Flow)转换为热流(Hot Flow)。

文件结构概览

scss 复制代码
Share.kt
├── shareIn() - 转换为 SharedFlow(共享流)
├── stateIn() - 转换为 StateFlow(状态流)
├── asSharedFlow()/asStateFlow() - 只读转换
└── onSubscription() - 订阅监听

一、shareIn - 共享流转换

核心作用

冷流 (每次收集都重新执行)转换为 热流(多个订阅者共享同一个上游流实例)。

函数签名

kotlin 复制代码
public fun <T> Flow<T>.shareIn(
    scope: CoroutineScope,           // 协程作用域
    started: SharingStarted,         // 启动策略
    replay: Int = 0                  // 重放数量(新订阅者能收到最近几个值)
): SharedFlow<T>

三种启动策略

策略 行为 适用场景
SharingStarted.Eagerly 立即启动,不管有没有订阅者 需要预加载数据,如应用启动时获取配置
SharingStarted.Lazily 第一个订阅者出现时启动,无订阅者时保持缓存 延迟初始化,但保留最新状态
SharingStarted.WhileSubscribed() 有订阅者时运行,无订阅者时停止(可配置超时) 节省资源,如UI相关的数据流

使用示例

kotlin 复制代码
// 场景:昂贵的网络连接需要共享
class MessageRepository {
    private val backendMessages: Flow<Message> = flow {
        connectToBackend() // 耗时操作
        while (true) {
            emit(receiveMessage())
        }
    }
    
    // 共享连接, eager 启动提前建立连接
    val messages: SharedFlow<Message> = backendMessages
        .shareIn(
            scope = viewModelScope,
            started = SharingStarted.Eagerly,
            replay = 1  // 新订阅者收到最近1条消息
        )
}

// 多个收集者共享同一个连接
viewModelScope.launch { messages.collect { /* UI 更新 */ } }
viewModelScope.launch { messages.collect { /* 日志记录 */ } }

异常处理与完成处理

kotlin 复制代码
backendMessages
    .onCompletion { cause -> 
        if (cause == null) emit(CompletionSignal) 
    }
    .catch { e -> 
        // 自定义异常处理
        emit(ErrorMessage(e))
    }
    .retry { e -> e is IOException }
    .shareIn(scope, SharingStarted.Eagerly)

二、stateIn - 状态流转换

核心作用

专门用于管理 状态 的共享流,总是保留最新值,类似 BehaviorSubject。

两种重载形式

1. 带初始值的 stateIn(立即返回)

kotlin 复制代码
public fun <T> Flow<T>.stateIn(
    scope: CoroutineScope,
    started: SharingStarted,
    initialValue: T           // 必须有初始值
): StateFlow<T>

2. 挂起式 stateIn(等待首个值)

kotlin 复制代码
public suspend fun <T> Flow<T>.stateIn(scope: CoroutineScope): StateFlow<T>
// 挂起直到上游发出第一个值,适合没有合适默认值的情况

使用示例

kotlin 复制代码
class UserViewModel : ViewModel() {
    private val userIdFlow: Flow<String> = dataStore.userId
    
    // 方式1:带初始值,立即可用
    val userState: StateFlow<User> = userRepository.getUserFlow(userIdFlow)
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000), // 5秒超时
            initialValue = User.Loading
        )
    
    // 方式2:挂起式,等待首个真实值(在挂起函数中使用)
    suspend fun getUserState(): StateFlow<User> {
        return userRepository.getUserFlow(userIdFlow)
            .stateIn(viewModelScope)  // 挂起直到第一个用户数据到达
    }
}

// UI 层使用
@Composable
fun UserScreen(viewModel: UserViewModel) {
    val user by viewModel.userState.collectAsStateWithLifecycle()
    // 自动管理生命周期,WhileSubscribed 确保不在前台时停止收集
}

三、内部实现机制

1. 配置融合(Operator Fusion)

kotlin 复制代码
private fun <T> Flow<T>.configureSharing(replay: Int): SharingConfig<T>
  • 检测上游是否有 bufferflowOn 等操作符
  • 智能融合,避免不必要的中间通道

2. 共享协程启动

kotlin 复制代码
private fun <T> CoroutineScope.launchSharing(...)
  • Eagerly : CoroutineStart.DEFAULT - 延迟到调度器可用时启动
  • 其他 : CoroutineStart.UNDISPATCHED - 立即在当前协程启动,确保订阅者先注册

3. 命令处理(自定义策略)

kotlin 复制代码
started.command(shared.subscriptionCount)
    .distinctUntilChanged()
    .collectLatest { command ->
        when (command) {
            SharingCommand.START -> upstream.collect(shared)
            SharingCommand.STOP -> { /* 取消收集 */ }
            SharingCommand.STOP_AND_RESET_REPLAY_CACHE -> shared.resetReplayCache()
        }
    }

四、asSharedFlow / asStateFlow

可变流 暴露为 只读流(API 设计最佳实践):

kotlin 复制代码
class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()  // 外部只读
    
    private val _events = MutableSharedFlow<Event>()
    val events: SharedFlow<Event> = _events.asSharedFlow()
    
    fun updateState(newState: UiState) {
        _uiState.value = newState  // 内部可写
    }
}

五、onSubscription - 订阅监听

kotlin 复制代码
public fun <T> SharedFlow<T>.onSubscription(
    action: suspend FlowCollector<T>.() -> Unit
): SharedFlow<T>

执行时机 :订阅者注册后,收到第一个值之前

典型场景:发送初始事件或确认信号

kotlin 复制代码
messageFlow
    .onSubscription { 
        emit(Message("Connected", System.currentTimeMillis())) 
    }
    .shareIn(scope, SharingStarted.Lazily)

六、选型决策树

ini 复制代码
需要共享数据给多个收集者?
├── 否 → 直接使用 Flow(冷流)
└── 是 → 需要永远保留最新值?
    ├── 是 → 使用 stateIn(StateFlow)
    │         └── UI 状态、配置数据、用户信息
    └── 否 → 使用 shareIn(SharedFlow)
              ├── 需要事件历史?设置 replay > 0
              └── 纯事件通知?replay = 0

七、关键注意事项

  1. 作用域生命周期shareIn/stateIn 的 scope 决定流存活时间,通常使用 viewModelScopelifecycleScope

  2. 内存泄漏WhileSubscribed 策略配合 replayExpirationMillis 可设置缓存过期

  3. 线程安全 :StateFlow 的 value 读写是线程安全的,但 compareAndSet 需要处理竞争

  4. 背压处理 :通过前置 bufferconflate 操作符配置缓冲策略

这个文件是 Kotlin 协程 Flow 共享机制的核心实现,理解它对于构建响应式 Android 应用架构至关重要。

相关推荐
stevenzqzq1 分钟前
Kotlin 协程:withContext 与 async 核心区别与使用场景
android·开发语言·kotlin
唔665 分钟前
原生 Android(Kotlin)仅串口「侵入式架构」完整案例三
android·架构·kotlin
唔666 分钟前
原生 Android(Kotlin)仅串口「可插拔架构」完整案例一
android·架构·kotlin
Melrose11 分钟前
移动端安全攻防
android·前端·安全
葡萄城技术团队28 分钟前
Claude Code Buddy 小析:一个非核心功能,如何体现产品的细节完成度
android·java·microsoft
.豆鲨包1 小时前
【Android】OkHttp的使用及封装
android·java·okhttp
黄林晴1 小时前
重启不用输 PIN!Android 17 终于把 SIM 卡安全做明白了
android
2501_915921431 小时前
uni-app一键生成iOS安装包并上传TestFlight全流程
android·ios·小程序·https·uni-app·iphone·webview
studyForMokey1 小时前
【Android面试】四大组件专题 todo
android·面试·职场和发展
qq_353737541 小时前
网站 Favicon 获取 API 技术实现指南
android