别再写“上帝 Activity”了!一文讲透 Android 官方推荐的 MVVM 架构
2026/6/17 17:03:22 网站建设 项目流程

把所有代码都写在 Activity / Fragment 里会怎样?一个文件几千行、网络请求和界面逻辑搅在一起、屏幕一旋转数据就丢、想加个单元测试无从下手……这就是所谓的"上帝 Activity"。MVVM是 Google 官方推荐的应用架构,它通过分层把界面、状态、数据清晰地隔开,让代码可维护、可测试、抗配置变更。本文讲清楚 MVVM 到底是什么、每层做什么、以及它和官方架构指南的关系。

一、为什么需要架构:从"上帝 Activity"说起

新手最常见的写法,是把一切塞进 Activity:

classUserActivity:AppCompatActivity(){overridefunonCreate(s:Bundle?){// 网络请求、JSON 解析、数据库读写、界面更新……全在这thread{valjson=URL(api).readText()valuser=parse(json)runOnUiThread{textView.text=user.name}}}}

问题在哪?

  • 职责混乱:UI、业务逻辑、数据访问全耦合在一起;
  • 抗不住配置变更:屏幕一旋转 Activity 重建,请求重来、数据丢失;
  • 没法测试:业务逻辑绑死在 Activity 上,无法脱离 Android 框架单测;
  • 难以维护:文件越来越大,改一处牵一发动全身。

架构的本质:通过分离关注点(Separation of Concerns),让每一块代码只干一件事。MVVM 就是一种成熟的分离方案。


二、MVVM 的三层结构

MVVM = Model–View–ViewModel。核心思想是把界面从业务和数据中解耦:

是谁负责什么不该做什么
ViewActivity / Fragment / Composable显示界面、观察状态、把用户操作上报给 ViewModel不写业务逻辑、不直接访问数据
ViewModelandroidx.lifecycle.ViewModel持有并暴露 UI 状态、处理界面逻辑、调用数据层不持有 View 引用、不碰 Android UI 类
ModelRepository + 数据源提供数据(网络、数据库、缓存)不关心界面

关键的"单向"关系

  • View 知道 ViewModel,ViewModel 不知道 View。ViewModel 只暴露状态,谁来观察它都行;
  • View观察状态变化(响应式),而不是被动等 ViewModel “推”;
  • 这种单向依赖让 ViewModel 完全独立于界面,可以被单独测试。

一句话:ViewModel 不能持有Activity/View/Context(Application Context 例外)的引用,否则配置变更时会内存泄漏,也破坏了可测试性。


三、ViewModel:为什么它能"扛住屏幕旋转"

ViewModel是 Jetpack 提供的类,它最大的特点是生命周期比 Activity/Fragment 更长

  • 屏幕旋转、语言切换等配置变更导致 Activity 重建时,ViewModel 实例会被保留,数据不丢;
  • 只有当 Activity真正结束(用户按返回退出、finish())时,ViewModel 才会收到onCleared()并销毁。
classUserViewModel:ViewModel(){privateval_uiState=MutableStateFlow<UiState>(UiState.Loading)valuiState:StateFlow<UiState>=_uiState.asStateFlow()funloadUser(id:Int){viewModelScope.launch{// 协程作用域随 ViewModel 自动取消_uiState.value=UiState.Loading runCatching{repository.getUser(id)}.onSuccess{_uiState.value=UiState.Success(it)}.onFailure{_uiState.value=UiState.Error(it.message)}}}overridefunonCleared(){// ViewModel 销毁时回调// 清理资源(viewModelScope 会自动取消,无需手动)}}

配合协程:viewModelScope启动的协程会在 ViewModel 销毁时自动取消,完美契合。


四、UI 状态

MVVM 的现代实践强调UI State(界面状态)的概念:用一个不可变的数据对象,完整描述界面当前该显示什么。ViewModel 暴露它,View 观察它。

常见做法是用密封类/接口表达互斥状态:

sealedinterfaceUiState{dataobjectLoading:UiStatedataclassSuccess(valuser:User):UiStatedataclassError(valmessage:String?):UiState}

或用一个 data class 聚合所有界面字段:

dataclassUserUiState(valisLoading:Boolean=false,valuser:User?=null,valerrorMessage:String?=null)

View 只需"照着状态渲染":

// Compose@ComposablefunUserScreen(viewModel:UserViewModel){valstatebyviewModel.uiState.collectAsStateWithLifecycle()when(state){isUiState.Loading->LoadingSpinner()isUiState.Success->UserDetail((stateasUiState.Success).user)isUiState.Error->ErrorView((stateasUiState.Error).message)}}

单一数据源(Single Source of Truth):界面该显示什么,只由 ViewModel 暴露的状态决定。View 不再自己维护一堆零散的isLoadingdata变量,而是"渲染同一个状态对象"。这让界面行为可预测、易调试。


五、Repository:数据层的"统一入口"

ViewModel 不应直接调用 Retrofit 或 Room,而是通过Repository(仓库)访问数据。Repository 是数据层的门面,负责:

  • 整合多个数据源(网络 API + 本地数据库 + 缓存);
  • 决定数据从哪来(如:先读缓存,再请求网络更新);
  • 向上层暴露干净的数据接口,屏蔽底层细节。
classUserRepository(privatevalapi:UserApi,privatevaldao:UserDao){suspendfungetUser(id:Int):User{// 例:先尝试本地,没有再请求网络并缓存returndao.find(id)?:api.fetchUser(id).also{dao.insert(it)}}}

好处:ViewModel 不关心数据"从网络还是数据库来",只管"要一个 User"。将来换数据源、加缓存策略,只改 Repository,上层无感知。


六、官方架构指南:MVVM 的"全景图"

Google 的应用架构指南推荐把应用分为两到三层,MVVM 正好落在这套分层里:

  • UI 层:渲染界面 + 持有状态(View + ViewModel);
  • 领域层(可选):当业务逻辑复杂或被多个 ViewModel 复用时,抽出UseCase;简单项目可省略;
  • 数据层:Repository + 各数据源,是数据的"权威来源"。

依赖方向是单向向下:UI 依赖数据层,数据层不依赖 UI。

MVVM 与官方架构的关系:官方架构指南是更完整的工程蓝图,而 MVVM 主要描述了其中UI 层View 与 ViewModel 的关系。实践中我们说"用 MVVM",通常就是指"ViewModel + 状态 + Repository"这一整套。


七、依赖注入:让各层"松耦合"地组装起来

ViewModel 需要 Repository,Repository 需要 Api 和 Dao……这些依赖如果手动new,会到处硬编码、难以替换和测试。依赖注入(DI)把"创建依赖"和"使用依赖"分开。Android 官方方案是Hilt

@HiltViewModelclassUserViewModel@Injectconstructor(privatevalrepository:UserRepository// 自动注入):ViewModel(){/* ... */}

测试时可以注入一个假的Repository,脱离真实网络验证 ViewModel 逻辑——这正是分层 + DI 带来的可测试性。Hilt 细节可单独成篇,这里只点出它在架构中的位置:负责把各层装配起来。


八、MVVM 与 MVC、MVP 的区别

模式界面与逻辑的关系主要问题 / 特点
MVCController 处理逻辑,但在 Android 里 Activity 既是 View 又是 Controller职责不清,容易变成"上帝类"
MVPPresenter 持有 View 接口,双向调用解耦了,但 Presenter 持有 View 引用,需手动管理生命周期、写大量接口
MVVMViewModel不持有View,View 观察状态响应式、抗配置变更、易测试,是当前主流
MVI在 MVVM 基础上强调单一状态 + 单向数据流MVVM 的一种更严格的演进

趋势:MVP 因为要手写大量接口、Presenter 还得管 View 引用,已逐渐被 MVVM 取代。MVI 可以看作 MVVM 的"强化版",把"单一不可变状态 + 单向数据流"做到极致。


参考来源:

  • Android 官方文档 - 应用架构指南
  • UI 层
  • ViewModel 概览
  • 数据层

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询