Kotlin shareIn 和 stateIn 使用场景

4 阅读3分钟

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

Share.png

文件结构概览

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

一、shareIn - 共享流转换

核心作用

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

函数签名

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

三种启动策略

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

使用示例

// 场景:昂贵的网络连接需要共享
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 { /* 日志记录 */ } }

异常处理与完成处理

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(立即返回)

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

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

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

使用示例

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)

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

2. 共享协程启动

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

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

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 设计最佳实践):

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 - 订阅监听

public fun <T> SharedFlow<T>.onSubscription(
    action: suspend FlowCollector<T>.() -> Unit
): SharedFlow<T>

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

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

messageFlow
    .onSubscription { 
        emit(Message("Connected", System.currentTimeMillis())) 
    }
    .shareIn(scope, SharingStarted.Lazily)

六、选型决策树

需要共享数据给多个收集者?
├── 否 → 直接使用 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 应用架构至关重要。