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
| 特性 | StateFlow | SharedFlow |
|---|---|---|
| 初始值 | 必须有 | 不需要 |
| 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):
StateFlow、SharedFlow。无论有没有收集者都可能在发射数据,多个收集者共享同一数据流。类似「直播」。
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
}