1. 状态管理困境:为什么我们需要更好的方案?

说实话,我做了快八年的Android开发,踩过最多的坑就是状态管理。

你想想看,一个App里有多少状态?用户登录没、列表加载完没、弹窗显示不显示、表单填到哪一步了……这些看似简单的东西,一旦项目变大,就会变成一团乱麻。

我记得刚入行那会儿,带我的老大哥跟我说了一句话,我现在还记得:「代码烂不烂,看状态管理就知道。」

1.1 传统方式:那些年我们写过的面条代码

最早的时候,Android里根本没有所谓的状态管理方案。大家怎么干?直接往Activity里塞变量。

class LoginActivity : AppCompatActivity() {
    private var isLoading = false
    private var isLoggedIn = false
    private var errorMessage: String? = null
    private var username = ""
    private var password = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 各种逻辑混在一起
    }

    private fun login() {
        isLoading = true
        updateUI()
        // 网络请求...
    }
}

这种写法有什么问题?我简单列几条:

  • 状态散落一地——变量到处定义,改一个漏三个
  • 生命周期耦合——Activity一重建,状态全丢
  • 测试基本没戏——想测个登录逻辑?你得先跑起整个Activity

我在一个电商项目里就吃过这个亏。当时有个购物车页面,状态变量写了二十多个。后来产品说要加个「凑单满减」功能,我改了两天,改出三个bug。嗯,那感觉,真酸爽。

⚠️ 我曾经踩过的坑: 用全局变量存状态,结果用户切到后台再回来,状态全乱了。用户把商品加到购物车,回来一看购物车是空的——投诉电话直接打到我手机上。

1.2 为什么传统方式撑不住了?

说白了,传统方式的核心问题就一个:状态和UI绑得太死

你想想看,Activity既要管生命周期,又要管业务逻辑,还要管UI刷新。一个类几千行,谁看了不头疼?

我后来总结了一下,传统方式有三大硬伤:

问题 具体表现 后果
状态分散 变量散落在Activity、Fragment、Adapter里 改一处,崩三处
生命周期敏感 旋转屏幕、切后台都会导致状态丢失 用户体验极差
难以测试 业务逻辑和UI代码混在一起 单元测试覆盖率接近0

1.3 现代架构的演进:我们到底在追求什么?

后来大家开始反思:能不能把状态从UI里抽出来?

于是就有了MVP、MVVM、MVI这些架构模式。它们的核心思路其实都一样:让状态独立存在,UI只管订阅和展示

我个人习惯用MVVM,因为它和Android的Jetpack配合得最好。你看ViewModel加上LiveData或者StateFlow,基本就能解决大部分问题。

核心思路: 状态是数据,UI是视图。两者通过观察者模式解耦。状态变了,UI自动更新。UI只管「怎么画」,不管「为什么画」。

但这里有个新问题:状态本身怎么定义?

用普通类?用接口?用枚举?

我试过很多种方式。用普通类吧,容易写出各种非法状态。比如一个登录页面,既可以是「加载中」,又可以是「已登录」,这两个状态同时存在就出问题了。

用枚举吧,又太死板。你没法在枚举里附带额外信息,比如错误码、用户数据什么的。

1.4 状态管理的核心矛盾

说白了,状态管理要解决的就是三个问题:

  1. 状态怎么定义——既要精确,又要灵活
  2. 状态怎么流转——谁改的?什么时候改的?
  3. 状态怎么消费——UI怎么知道状态变了?

传统方式三个都没解决好。现代架构解决了后两个,但第一个——状态定义——依然是个难题。

你想想看,一个网络请求的状态,可能有:空闲、加载中、成功、失败。如果用普通类,你可能会写出这样的代码:

data class UiState(
    val isLoading: Boolean = false,
    val isSuccess: Boolean = false,
    val isError: Boolean = false,
    val errorMessage: String? = null,
    val data: List<Item>? = null
)

看着还行对吧?但问题来了:isLoading = trueisSuccess = true 同时为 true 怎么办?这种非法状态,编译器根本不会管你。

我在一个金融项目里就遇到过这种事。同事写了个状态类,加载中和成功两个标志位同时为true,结果UI同时显示了加载动画和成功提示——用户一脸懵。

💡 一个小技巧: 如果你发现自己的状态类里有很多 Boolean 字段,而且它们之间互斥,那就说明该用密封类了。

1.5 从混乱到有序:一张图看懂演进

下面这张图,是我自己总结的状态管理演进路线。你看一眼就能明白,我们为什么要追求更好的方案。

Android 状态管理演进路线 传统方式 Activity 内嵌变量 状态与UI强耦合 MVP/MVVM 状态抽离到Presenter 观察者模式解耦 MVI + 密封类 状态类型安全 非法状态编译期拦截 未来 各阶段对比 状态定义 ❌ 散落变量 ⚠️ 普通类 ✅ 密封类 非法状态 ❌ 运行时崩溃 ⚠️ 运行时崩溃 ✅ 编译期拦截 可测试性 ❌ 极差 ⚠️ 一般 ✅ 优秀

1.6 密封类:一个被低估的解决方案

看到这里你可能会问:那到底用什么来定义状态?

我的答案是:密封类(Sealed Class)

为什么?因为它能让你把状态定义得既精确又安全。每个状态是一个独立的子类,互斥的状态根本不可能同时存在。

举个例子:

sealed class LoginState {
    object Idle : LoginState()
    object Loading : LoginState()
    data class Success(val user: User) : LoginState()
    data class Error(val code: Int, val message: String) : LoginState()
}

你看,LoadingSuccess 是互斥的。编译器保证你不可能同时处于两个状态。这就是密封类的威力。

我在一个重构项目里用了密封类之后,状态相关的bug直接降了80%。不是夸张,是真的。

总结一下: 状态管理的本质,就是把「混乱」变成「有序」。传统方式靠人肉维护,现代架构靠模式约束,而密封类,则是从类型系统层面帮你兜底。

好了,这一章我们聊了为什么需要更好的状态管理方案。下一章,我会带你深入密封类的细节,看看它到底怎么用、用在哪儿、有哪些坑。


公众号:蓝海资料掘金营,微信deep3321