跳到主要内容

Flow 响应式流

问题

Kotlin Flow 在 Android 中如何使用?StateFlow 和 SharedFlow 的区别是什么?

答案

Flow 基础

Flow 是 Kotlin 协程的冷流(Cold Stream),只有在收集(collect)时才会执行:

// 创建 Flow
fun fetchUsers(): Flow<List<User>> = flow {
val users = api.getUsers()
emit(users) // 发射数据
}

// 收集 Flow
lifecycleScope.launch {
fetchUsers().collect { users ->
adapter.submitList(users)
}
}

StateFlow vs SharedFlow

特性StateFlowSharedFlow
初始值必须有不需要
Replay固定 1(当前值)可配置(0, 1, N)
去重✅ 相同值不重复发射❌ 每次都发射
典型用途UI 状态(替代 LiveData)事件(Toast、导航)

StateFlow 使用

class UserViewModel : ViewModel() {
// 可变的私有 StateFlow
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
// 不可变的公开 StateFlow
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

fun loadUser() {
viewModelScope.launch {
_uiState.value = UiState.Loading
try {
val user = repository.getUser()
_uiState.value = UiState.Success(user)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "未知错误")
}
}
}
}

sealed class UiState {
data object Loading : UiState()
data class Success(val user: User) : UiState()
data class Error(val message: String) : UiState()
}

// Activity 中收集
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showUser(state.user)
is UiState.Error -> showError(state.message)
}
}
}
}

SharedFlow 用于一次性事件

class UserViewModel : ViewModel() {
// replay = 0:不缓存旧事件
private val _events = MutableSharedFlow<UiEvent>()
val events: SharedFlow<UiEvent> = _events.asSharedFlow()

fun onSaveClicked() {
viewModelScope.launch {
try {
repository.save()
_events.emit(UiEvent.ShowToast("保存成功"))
_events.emit(UiEvent.NavigateBack)
} catch (e: Exception) {
_events.emit(UiEvent.ShowToast("保存失败: ${e.message}"))
}
}
}
}

sealed class UiEvent {
data class ShowToast(val message: String) : UiEvent()
data object NavigateBack : UiEvent()
}
StateFlow vs LiveData

StateFlow 可以完全替代 LiveData:

  • ✅ 协程原生支持
  • ✅ 必须有初始值(避免 null 状态)
  • ✅ 操作符丰富(map、filter、combine 等)
  • ✅ 不依赖 Android 平台,可在纯 Kotlin 模块使用

Flow 操作符

// 1. 转换操作符
repository.getUsersFlow()
.map { users -> users.filter { it.isActive } } // 转换
.filter { it.isNotEmpty() } // 过滤
.distinctUntilChanged() // 去重
.collect { activeUsers -> updateUI(activeUsers) }

// 2. 合并多个 Flow
val combined = combine(
userFlow,
settingsFlow
) { user, settings ->
UiState(user, settings)
}

// 3. flatMapLatest(搜索场景)
searchQueryFlow
.debounce(300) // 防抖 300ms
.distinctUntilChanged() // 相同查询不重复
.flatMapLatest { query -> // 取消上一次搜索
if (query.isBlank()) flowOf(emptyList())
else repository.search(query)
}
.collect { results -> showResults(results) }

// 4. catch 错误处理
repository.getDataFlow()
.catch { e -> emit(emptyList()) } // 捕获上游异常
.onStart { showLoading() } // 开始时
.onCompletion { hideLoading() } // 完成时
.collect { data -> showData(data) }

// 5. stateIn / shareIn(冷流转热流)
val userState: StateFlow<User?> = repository.getUserFlow()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000), // 无订阅者 5s 后停止
initialValue = null
)

Flow 与 Room 数据库

@Dao
interface UserDao {
// Room 自动生成 Flow,数据变化时自动发射
@Query("SELECT * FROM users ORDER BY name")
fun getAllUsers(): Flow<List<User>>
}

// ViewModel 中直接转为 StateFlow
class UserListViewModel(private val dao: UserDao) : ViewModel() {
val users: StateFlow<List<User>> = dao.getAllUsers()
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
}

常见面试问题

Q1: 冷流和热流的区别?

答案

  • 冷流(Cold Flow)flow { } 构建。每个收集者独立触发上游执行,没有收集者时不执行。类似「点播」。
  • 热流(Hot Flow)StateFlowSharedFlow。无论有没有收集者都可能在发射数据,多个收集者共享同一数据流。类似「直播」。

Q2: repeatOnLifecycle 的作用是什么?

答案

repeatOnLifecycle(State.STARTED) 在 Activity/Fragment 进入 STARTED 状态时开始收集 Flow,进入 STOPPED 状态时自动取消收集,再次回到 STARTED 时重新开始。这避免了在后台继续收集导致的资源浪费和 UI 更新崩溃。

// ❌ 不安全:Activity 在后台仍在收集
lifecycleScope.launch {
viewModel.uiState.collect { ... }
}

// ✅ 安全:跟随生命周期自动管理
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { ... }
}
}

Q3: SharingStarted.WhileSubscribed(5000) 是什么意思?

答案

当最后一个订阅者取消后,上游 Flow 等待 5 秒再停止。如果 5 秒内有新订阅者(如屏幕旋转后 Activity 重建),上游 Flow 继续运行,不会重新开始执行。这在配置变更时避免了不必要的数据重新加载。

Q4: 如何用 Flow 实现搜索防抖?

答案

private val _searchQuery = MutableStateFlow("")

val searchResults = _searchQuery
.debounce(300) // 300ms 内无新输入才执行
.distinctUntilChanged() // 相同 query 不重复搜索
.flatMapLatest { query -> // 新 query 取消上一次搜索
repository.search(query)
}
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

fun onQueryChanged(query: String) {
_searchQuery.value = query
}

相关链接