为什么需要架构组件

早期安卓把网络请求、数据处理、界面更新全塞进 Activity,很快就会发现两个致命问题:一是屏幕旋转重建 Activity 时数据全丢,二是 Activity 动辄几千行,改一处全身发抖。Jetpack 架构组件就是为解决这两件事而生。

它的核心思想是把"界面"和"数据"分开:ViewModel 持有数据与业务逻辑,不随屏幕旋转销毁;界面只负责观察数据变化并渲染。配合 LiveData 或 Flow,数据一变,界面自动刷新,你再也不用手动去 findViewById 更新控件。

架构组件的价值不是"多写几个类",而是让 Activity 瘦下来、让数据在配置变更中活下来。这两点做到了,代码质量会上一个台阶。

ViewModel:生命周期感知的数据持有者

ViewModel 的生命周期比 Activity 长:屏幕旋转时 Activity 被销毁重建,ViewModel 却保留下来,重建后拿到的还是同一个实例。这就是旋转后数据不丢的原理。

class UserViewModel : ViewModel() {
    private val _name = MutableStateFlow("")
    val name: StateFlow<String> = _name.asStateFlow()

    fun onNameChange(input: String) {
        _name.value = input.trim()
    }

    override fun onCleared() {
        super.onCleared()
        // 页面真正销毁时在这里释放资源
    }
}

两条铁律:ViewModel 里绝不能持有 Activity、View、Context 这类短生命周期引用,否则会内存泄漏;耗时任务用 viewModelScope 启动,页面销毁时自动取消,不会回调到已销毁的界面。

LiveData:可观察的数据容器

LiveData 是一个可观察的数据持有者,它自带生命周期感知:只有界面处于前台时才把数据推给观察者,避免界面不可见时的无用回调与崩溃。

LiveData 三要点
  • MutableLiveData私有可写,对外只暴露只读的 LiveData,防止外部随意改值。
  • observe传 LifecycleOwner,数据只在界面可见时回调。
  • liveData 构建器可用 liveData{} 协程块,把异步结果自动转成 LiveData。
  • 与 Flow 取舍新项目优先 Flow,LiveData 多用于老代码兼容。
安卓架构组件示意:三层悬浮圆角平台之间有向上流动的发光数据流与抽象节点,象征分层与响应式数据流
分层架构:界面只观察数据流,业务逻辑住在 ViewModel 里,互相通过数据解耦。

用 LiveData 时坚持"对外只读"原则:内部用 MutableLiveData 改值,暴露给界面的是只读 LiveData。这样数据的修改入口唯一,状态才不会被改乱。

Kotlin Flow:更现代的数据流

Flow 是 Kotlin 协程生态里的响应式流,能力比 LiveData 更强:可以做变换、合并、节流,能发射多个连续值。在 ViewModel 里最常用 StateFlow 持有界面状态。

class SearchViewModel : ViewModel() {
    private val query = MutableStateFlow("")

    // 输入变化 → 防抖 300ms → 触发搜索,典型的数据流串联
    val results: StateFlow<List<Item>> = query
        .debounce(300)
        .distinctUntilChanged()
        .mapLatest { repo.search(it) }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
}

几个关键操作符:debounce 防抖、distinctUntilChanged 去重、mapLatest 取消过期请求、stateIn 转成可观察状态流。把这些串起来,搜索框这种交互就不用自己写一堆计时器了。

界面状态:用密封类表达加载态

一个页面的数据状态通常是"加载中 / 成功 / 失败"三选一。用密封类把它表达出来,界面层用 when 穷尽处理,就不会出现"成功了却忘了隐藏加载圈"这类漏处理。

sealed interface UiState<out T> {
    data object Loading : UiState<Nothing>
    data class Success<T>(val data: T) : UiState<T>
    data class Error(val msg: String) : UiState<Nothing>
}

// 界面侧
when(state) {
    is UiState.Loading -> showLoading()
    is UiState.Success -> showContent(state.data)
    is UiState.Error   -> showError(state.msg)
}

这种写法的好处是编译器帮你检查分支是否齐全。以后加一种状态(比如空数据),所有 when 都会报出漏处理的位置,这正是密封类的价值。

Repository 分层与依赖

ViewModel 不直接碰网络或数据库,中间隔一层 Repository。Repository 决定数据从哪来:优先用缓存,缓存过期再拉网络,对外只暴露一个干净的接口。

  • 界面 → ViewModel:只观察状态,不关心数据来源。
  • ViewModel → Repository:发起请求,拿到 UiState。
  • Repository → 数据源:本地数据库或网络 API。

这样分层后,换数据源、加缓存、做单元测试都只动 Repository,界面和 ViewModel 不用改。依赖方向永远是从外向内,不要让 ViewModel 知道网络细节。