通俗易懂 + Android 开发实战的方式,详细讲讲 Kotlin 中的 StateFlow

一、一句话理解 StateFlow

StateFlow 是一个"永远有值"的 Flow,专门用来表示随时间变化的状态(比如加载中、成功、失败),并且会自动把最新状态推送给所有观察者。

你可以把它想象成一个:

  • 带默认值的"状态盒子"
  • 每次你往里放新值,所有正在观察它的人(比如 UI)都会立刻收到更新
  • 而且新来的观察者也能立刻拿到当前最新的值(不像普通 Flow 需要等下次发射)

二、为什么需要 StateFlow?(对比普通 Flow)

特性 普通 Flow StateFlow
❄️ 冷/热 冷流(每次 collect 才开始) 热流(一直存在,有初始值)
🧠 记住最新值 ❌ 不记 总是记住最新值
👥 多个观察者 每个 collect 独立执行 共享同一个状态,节省资源
📦 初始值 没有 必须提供初始值
🔄 重复值 可能重复发射 默认会跳过重复值(可配置)

👉 在 Android 中,UI 状态(如用户信息、列表数据、加载状态)天然就是"有当前值"的,所以 StateFlow 比普通 Flow 更合适!


三、基本用法:怎么创建和使用?

1. 在 ViewModel 中定义 StateFlow(推荐方式)
复制代码
class UserViewModel : ViewModel() {
    // 私有 MutableStateFlow,用于内部修改状态
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    
    // 对外暴露只读的 StateFlow
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    init {
        loadUser()
    }

    private fun loadUser() {
        viewModelScope.launch {
            try {
                val user = userRepository.getUser()
                _uiState.value = UiState.Success(user) // 更新状态
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

// 状态密封类(前面学过的!)
sealed class UiState {
    object Loading : UiState()
    data class Success(val user: User) : UiState()
    data class Error(val message: String) : UiState()
}
2. 在 Activity / Fragment / Compose 中收集状态

Jetpack Compose 示例:

复制代码
@Composable
fun UserProfileScreen(viewModel: UserViewModel) {
    val uiState by viewModel.uiState.collectAsState() // 自动转为 State

    when (uiState) {
        is UiState.Loading -> CircularProgressIndicator()
        is UiState.Success -> Text("Hello, ${uiState.user.name}!")
        is UiState.Error -> Text("Error: ${uiState.message}")
    }
}

View 系统(Activity/Fragment)示例:

复制代码
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { state ->
            when (state) {
                is UiState.Loading -> showProgressBar()
                is UiState.Success -> updateUI(state.user)
                is UiState.Error -> showError(state.message)
            }
        }
    }
}

🔔 注意:在 View 系统中要用 repeatOnLifecycle 避免内存泄漏!


四、StateFlow 的三大核心特性

1. 必须有初始值
复制代码
val state = MutableStateFlow(0) // ✅ OK
// val state = MutableStateFlow() // ❌ 编译错误!必须给初始值
2. 自动去重(conflation)
复制代码
val counter = MutableStateFlow(0)
counter.value = 1
counter.value = 1 // 这个不会触发下游更新(因为值没变)
counter.value = 2 // 会触发更新

如果你想允许重复值,可以用 distinctUntilChanged { false } 或改用 SharedFlow

3. 新订阅者立即收到最新值
  • 即使你在 10 秒后才开始 collect,也能立刻拿到当前状态。
  • 非常适合 UI:页面回来时自动恢复状态。

五、StateFlow vs LiveData(老朋友对比)

功能 LiveData StateFlow
生命周期感知 ✅(自动 stop/start) ❌(需配合 repeatOnLifecycle
Kotlin 协程支持 ⚠️ 弱(需 liveData builder) ✅ 原生支持
空安全 ❌ 可为 null ✅ 非空(除非你显式用 StateFlow<User?>
转换操作符 有限(map, switchMap) ✅ 完整 Flow 操作符(map, filter...)
测试 较难 ✅ 易于测试(纯 Kotlin)
推荐 旧项目 新项目首选

📌 Google 官方现在更推荐在新项目中使用 StateFlow(尤其搭配 Compose)。


六、常见问题 & 最佳实践

✅ 最佳实践1:对外暴露只读 StateFlow
复制代码
private val _uiState = MutableStateFlow(...)
val uiState: StateFlow<...> = _uiState.asStateFlow()

防止外部直接修改状态!

✅ 最佳实践2:用 sealed class 表示状态

避免 null 和模糊状态,让 when 分支全覆盖。

✅ 最佳实践3:在 Repository 层也用 StateFlow

比如 Room 数据库查询可以直接返回 Flow,在 ViewModel 中转成 StateFlow

复制代码
val users: StateFlow<List<User>> = 
    userRepository.getAllUsers()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000), // 5秒内无观察者则停止
            initialValue = emptyList()
        )

stateIn 是把普通 Flow 转成 StateFlow 的快捷方式!


七、总结一句话

StateFlow 是 Kotlin 协程生态中专为"UI 状态管理"设计的热流,它始终持有最新值、自动去重、支持多观察者,是现代 Android 开发(尤其是 Jetpack Compose)中替代 LiveData 的首选方案。

相关推荐
FreeBuf_1 分钟前
恶意 Rust 包瞄准 Web3 开发者窃取加密货币
开发语言·rust·web3
豐儀麟阁贵2 分钟前
9.4字符串操作
java·linux·服务器·开发语言
我的offer在哪里4 分钟前
MySQL 高频细节问题(覆盖性能、存储、运维、故障排查,补充前文未深入的核心细节)
android·运维·mysql
qq_717410015 分钟前
删除设置-声音-有来电时响铃并振动,手机铃声
android
dazhong20128 分钟前
Android Studio 安装之历史版本下载问题解决
android·ide·android studio
weixin_3077791311 分钟前
Jenkins Gson API插件:统一JSON处理的基础库
java·运维·开发语言·架构·jenkins
高频交易dragon12 分钟前
python缠论形态分析过程
开发语言·网络·python
fufu031113 分钟前
Linux环境下的C语言编程(三十六)
linux·c语言·开发语言·数据结构·算法
rit843249919 分钟前
LTE系统资源分配MATLAB实现示例(基于OFDMA的动态调度)
开发语言·matlab
灰度少爷26 分钟前
安卓手机投屏软件——QtScrcpy!
android·智能手机·安卓手机投屏软件