1. 核心结论
| 结论 | 说明 |
|---|---|
| 官方架构指引 | Google 官方不指定某一种具体模式(MVC/MVP/MVVM),而是推荐分层架构 + 单向数据流(UDF),强调关注点分离和状态可预测。[1][2] |
| 业界主流实践 | MVVM + 单一 UiState(吸收了 MVI 思想)是目前最常用的 UI 架构,能较好地解决复杂状态的同步问题。[20] |
| 推荐技术栈 | UI:Compose;状态:StateFlow;依赖注入:Hilt(官方推荐,并非强制);数据:Repository。[2][8] |
| 导航演进 | Navigation 3 把回退栈交给开发者以状态的形式管理,导航变成状态驱动,与 Compose 的声明式写法一致。[21] |
2. 架构演进时间线
Android 应用架构大致经历了四个阶段(年份为大致区间,用于说明趋势,不是严格的起止点):
| 阶段 | 大致时间 | 代表模式 | 特征 | 遗留问题 |
|---|---|---|---|---|
| 早期 | 2008 – 2013 | MVC | Massive Activity,视图和逻辑严重耦合 | 难以做单元测试,维护成本高 |
| 解耦期 | 2013 – 2017 | MVP | 用 Contract 接口把视图和逻辑分开 | 接口数量激增,生命周期管理仍然复杂 |
| 数据驱动期 | 2017 – 2021 | MVVM | ViewModel + LiveData(2017 年 Architecture Components 发布),数据变化驱动视图更新 | 多个数据源导致状态分散,一致性难以追踪 |
| 原则驱动期 | 2021 – 至今 | MVI / UDF | 用单一 UiState 集中管理页面状态,单向数据流 | 需要设计 UiState 和事件模型 |
MVC ──▶ MVP ──▶ MVVM ──▶ MVVM + 单一 UiState(MVI 风格)/ UDF
耦合 接口解耦 数据驱动 状态集中、数据单向流动
3. MVC:原理与优缺点
Model-View-Controller
*图:MVC 架构(Activity/Fragment 同时充当 View 与 Controller)
核心痛点:在 Android 中,Activity 既负责 UI 初始化(View),又负责事件处理(Controller),两者边界模糊,最终变成 Massive View Controller。
优势
- 结构简单直观:概念好理解,符合事件驱动编程的直觉。
- 上手成本低:不需要额外的框架或抽象层。
劣势
- Massive View Controller:业务逻辑和 UI 逻辑混在 Activity 里,代码量大,难以维护。
- 难以单元测试:Controller 强依赖 Android SDK(Context、View),无法在纯 JVM 环境中测试。
- 状态管理脆弱:屏幕旋转等配置变更会重建 Activity,不做特殊处理就会丢失 UI 状态。
适用场景:不建议用于现代 Android 生产项目,仅适合教学演示、极简原型验证(PoC)或一次性工具页面。
4. MVP:原理与特性分析
*图:MVP 架构(View 与 Presenter 通过 Contract 接口通信)
核心优势
通过 Contract 接口把 View 和 Presenter 分开。Presenter 不依赖 Android SDK,可以做纯 JVM 单元测试,测试效率和代码可靠性都明显提升。
主要痛点
- 接口激增:每个页面都要定义 IView/IPresenter 接口,模板代码多。
- 生命周期管理:Presenter 持有 View 引用。用
WeakReference只能让 View 可以被回收,进行中的网络请求或协程仍要在detachView()时手动取消,否则容易内存泄漏或回调到已销毁的界面。[19][24]
配置变更处理
原生 MVP 不好处理屏幕旋转等配置变更。社区常用 Mosby 框架,借助 Fragment 的 setRetainInstance(true) 等机制保留 Presenter 实例。[19][24](setRetainInstance 在新版 Fragment 中已被废弃,这个方案主要见于老项目。)
示例(Kotlin)
interface UserContract {
interface View { fun showLoading(); fun showUser(user: User); fun showError(msg: String) }
interface Presenter { fun attach(view: View); fun detach(); fun loadUser(id: String) }
}
class UserPresenter(private val repo: UserRepository) : UserContract.Presenter {
private var view: UserContract.View? = null
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
override fun attach(view: UserContract.View) { this.view = view }
override fun detach() { view = null; scope.coroutineContext.cancelChildren() } // 必须手动取消
override fun loadUser(id: String) {
view?.showLoading()
scope.launch {
runCatching { repo.getUser(id) }
.onSuccess { view?.showUser(it) }
.onFailure { view?.showError(it.message ?: "error") }
}
}
}
适用场景:已有 MVP 架构、暂时不迁移的老项目。
5. MVVM:原理与分析
*图:MVVM 架构(View 观察 ViewModel 暴露的状态)
核心原理
- ViewModel 不持有 View:两者通过观察者模式通信。
- 状态驱动 UI:UI 是状态的映射,数据变化后界面自动刷新。
优势
- 配置变更后数据保留:屏幕旋转不丢数据。
- 内存泄漏风险低:ViewModel 不引用 View,且感知生命周期(前提是不持有 View/Activity Context)。
- 可测试性高:业务逻辑独立于 Android 框架。
挑战
- 状态定义繁琐:需要仔细设计 UiState 数据类。
- 一次性事件:Toast、导航等事件要谨慎处理。官方建议优先把事件建模成 UI 状态,由 UI 处理完后通知 ViewModel 清除;Channel 等方案属于社区做法,各有取舍。[4]
示例(Kotlin)
data class UserUiState(
val isLoading: Boolean = false,
val user: User? = null,
val errorMessage: String? = null
)
@HiltViewModel
class UserViewModel @Inject constructor(
private val repo: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(UserUiState())
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser(id: String) {
_uiState.update { it.copy(isLoading = true) }
viewModelScope.launch {
runCatching { repo.getUser(id) }
.onSuccess { u -> _uiState.update { it.copy(isLoading = false, user = u) } }
.onFailure { e -> _uiState.update { it.copy(isLoading = false, errorMessage = e.message) } }
}
}
fun errorShown() = _uiState.update { it.copy(errorMessage = null) }
}
适用场景:目前的主流选择,也是新项目的首选。官方的推荐模式(ViewModel 作为状态持有者 + UDF)与它最接近,结合 Jetpack 组件(StateFlow、Compose 等)效果最好。
6. 三种架构 9 维度对比
| 对比维度 | MVC | MVP | MVVM |
|---|---|---|---|
| 1. 核心思想 | 视图与模型分离,控制器调度 | 面向接口编程,切断 V 与 M 的直接联系 | 数据驱动视图,响应式编程 |
| 2. 职责划分 | Activity/Fragment 既是 V 也是 C,容易臃肿 | 职责清晰,Presenter 处理纯逻辑 | 职责清晰,ViewModel 专注状态管理 |
| 3. 引用关系 | View 依赖 Model,Controller 依赖两者 | View 与 Presenter 互相持有引用 | View 单向依赖 ViewModel |
| 4. 可测试性 | 差(强依赖 Android SDK) | 良好(可 Mock View 接口测试 P 层) | 很好(ViewModel 纯逻辑,无需 Mock View) |
| 5. 耦合度 | 高 | 中(通过接口契约绑定) | 低(通过观察者模式松耦合) |
| 6. 样板代码量 | 少 | 多(大量 View/Presenter 接口) | 中(UiState 类 + 观察代码) |
| 7. 内存泄漏风险 | 高(异步回调容易持有 Activity) | 较高(需手动解绑 View、取消任务) | 低(ViewModel 不持有 View,感知生命周期) |
| 8. 配置变更支持 | 差(旋转后数据丢失) | 差(需手动保存/恢复或借助 Mosby) | 好(ViewModel 实例自动保留) |
| 9. 学习成本 | 低 | 中(需理解接口隔离) | 中高(需掌握协程/Flow 等响应式编程) |
7. 按项目类型选型
| 项目类型 | 推荐架构 | 理由 |
|---|---|---|
| 新项目(Compose) | MVVM + UDF | 与声明式 UI 的数据驱动特性契合。UDF 让状态可预测,ViewModel 负责管理 UI 状态,与官方推荐架构一致。 |
| 遗留 View 项目 | MVP 或渐进式 MVVM | 保持现有 MVP 稳定运行,避免大规模重构的风险;新功能模块逐步引入 MVVM,平滑过渡。 |
| 复杂交互 / 金融类 | MVI 风格的 MVVM | 状态变化频繁、对一致性要求严格时,Intent 驱动和单一状态源能减少状态冲突,提高健壮性。 |
| 超大型团队 | MVVM + 深度模块化 | 结合 Clean Architecture 思想,按业务边界拆分模块;通过依赖注入和接口隔离,支持多团队并行开发。 |
8. 官方架构指南:分层结构
图:Android 官方推荐的应用分层架构(来源:developer.android.com [1])
| 层级 | 职责 | 主要组成 |
|---|---|---|
| UI 层(UI Layer) | 在屏幕上显示应用数据,响应用户交互 | UI 元素(Activity / Compose)、状态持有者(ViewModel) |
| 领域层(Domain Layer,可选) | 位于 UI 层和数据层之间,封装复杂业务逻辑,或被多个 ViewModel 复用的简单逻辑,避免重复 [5] | UseCase / Interactor |
| 数据层(Data Layer) | 包含应用数据和业务逻辑,遵循单一数据源(SSOT)原则,保证数据一致可靠 [6] | Repository(SSOT)、数据源(网络 / 数据库) |
9. 官方架构指南:单向数据流(UDF)
图:UI 层单向数据流(UDF)示意(来源:developer.android.com [3])
核心原则
- 状态向下流动(State flows down)
- 事件向上流动(Events flow up)
工作流程
- 数据层向 ViewModel 提供应用数据。
- ViewModel 处理数据,转换成 UI State 暴露给界面。
- UI 元素读取状态并渲染界面。
- 用户交互产生事件(Events),传回 ViewModel 处理,形成闭环。
说明:官方的“事件处理决策树”配图链接失效(404),本文用文字代替:UI 逻辑(如展开/折叠)在 UI 内处理;业务逻辑交给 ViewModel;ViewModel 产生的一次性事件优先建模为 UI 状态。[4]
10. 状态容器与 View 绑定对比
10.1 状态容器(State Holders)
| 方案 | 定位 | 特点 | 适用场景 |
|---|---|---|---|
| LiveData | 传统方案 | 感知生命周期;setValue 只能在主线程调用,后台线程要用 postValue;操作符少 | 简单的 UI 状态观察,Java 项目或遗留项目 |
| StateFlow | 官方推荐 | 必须有初始值,天然符合 UI 状态的连续性;基于协程,可切换 Dispatcher;自带去重(相同值不重复发射) | 暴露 UI 状态(UiState) |
| SharedFlow | 事件流 | 没有初始值;replay 和 buffer 策略可配置 | 多订阅者的事件广播;官方不建议用它发送一次性 UI 事件(如 Toast) |
在 Compose 或 Lifecycle 中收集 StateFlow 时,建议使用
collectAsStateWithLifecycle()/repeatOnLifecycle,避免界面在后台时继续收集。
10.2 视图绑定
| 方案 | 特点 | 建议 |
|---|---|---|
| ViewBinding(首选) | 不需要注解处理,构建开销小;自动生成绑定类,类型安全、空安全,避免 findViewById 导致的 NPE | 基于 View 的项目首选 |
| DataBinding | 支持双向绑定,可以在 XML 中写表达式;需要注解处理,大型项目构建较慢;逻辑混在 XML 里不好排查 | 新项目一般不推荐;Compose 项目不需要 |
11. Compose 状态提升与 Navigation 3
11.1 状态提升(State Hoisting)[7]
把状态移到使用它的各个可组合项的最低公共祖先,遵循 UDF:状态以参数形式向下传,事件以 Lambda 形式向上传。
图:状态向下、事件向上的状态提升示意,来源:本 PPT 第 13 页
| 状态持有者 | 管理内容 |
|---|---|
| Composable 本身 | 简单的 UI 元素状态(如展开/折叠) |
| 普通 State Holder 类 | 复杂的 UI 逻辑和状态 |
| ViewModel | 业务逻辑和屏幕级状态,生命周期更长 |
@Composable
fun UserRoute(viewModel: UserViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
UserScreen(uiState = uiState, onRetry = { viewModel.loadUser("42") })
}
@Composable
fun UserScreen(uiState: UserUiState, onRetry: () -> Unit) { /* 无状态、易预览、易测试 */ }
11.2 Navigation 3:状态驱动导航 [21]
Navigation 3 用 Compose 的响应式写法取代命令式导航:
- 回退栈由开发者持有:回退栈是一个可观察的状态列表,导航就是对这个列表的增删,UI 根据它自动渲染。
- 类型安全:路由是 Kotlin 类型(可配合 Kotlin Serialization),不再拼接字符串路由。
// 示意代码:表达“导航即状态变更”的思路,具体 API 以官方文档为准
@Serializable data object Home : NavKey
@Serializable data class Profile(val id: String) : NavKey
val backStack = rememberNavBackStack(Home)
NavDisplay(
backStack = backStack,
onBack = { backStack.removeLastOrNull() },
entryProvider = entryProvider {
entry<Home> { HomeScreen(onOpen = { id -> backStack.add(Profile(id)) }) }
entry<Profile> { key -> ProfileScreen(key.id) }
}
)
12. MVI 与 MVVM 融合
MVI 的核心理念
- 单一状态对象:UI 状态放在一个不可变数据类里,避免多个状态流之间不一致。
- 显式 Intent:用户操作抽象成明确的意图(Intent/Action),统一交给 ViewModel。
- 纯函数 Reducer:根据当前状态和新事件,用纯函数算出新状态,结果可预测。
现代 MVVM 吸收了哪些做法
用单一 UiState 聚合所有状态,取代多个 LiveData:
data class NewsUiState(
val isLoading: Boolean = false,
val articles: List<Article> = emptyList()
)
用 MutableStateFlow.update 原子地更新状态,作用类似 Reducer:
// 线程安全的原子更新
_uiState.update { currentState ->
currentState.copy(isLoading = true)
}
注意:“UDF 本质是 MVI 变体”主要是社区观点 [20],不是官方定义。比较稳妥的说法是:官方推荐架构(UDF + StateFlow)是吸收了 MVI 优点的改良版 MVVM。
13. Clean Architecture 与模块化
13.1 Clean Architecture 核心 [12]
- 依赖规则:源码依赖只能向内,指向更高层的策略。
- 外层(UI、DB、Network)依赖内层(Entities、UseCases),内层不知道外层的存在。
- 业务逻辑独立于框架、UI 和数据库,便于测试。
与官方分层的关系:官方架构中领域层是可选的,依赖方向为 UI → Domain → Data。这比经典 Clean Architecture 更轻量、更务实。[1][5]
13.2 UseCase 的争议与实践
- 争议点:如果 UseCase 只是简单转发(ViewModel → UseCase → Repository),就成了多余的样板代码。
- 实践建议(参考 Now in Android [9]):
- 用 UseCase 封装复杂业务逻辑;
- 用来组合多个 Repository;
- 避免纯转发;
- 按需引入:简单 CRUD 直接调用 Repository,复杂交互再引入 UseCase。
13.3 现代 Android 模块化分层
模块化可以支持多团队并行开发、提升编译速度,并强制隔离模块边界。
图:现代 Android 模块化分层(:app → :feature → :core),来源:本 PPT 第 15 页
14. 应用案例(一):官方示例
| 项目 | 定位 | 亮点 |
|---|---|---|
| architecture-samples [10] | 官方架构蓝图示例 | main 分支:Compose + Hilt + Flow;views 分支:XML + Navigation |
| Now in Android(NiA) [9] | 现代 Android 开发示范项目 | 官方架构学习路径文档中的分步响应式数据流讲解;离线优先(Offline-first)架构 |
| Sunflower [11] | 园艺应用示例 | 从 View 迁移到 Compose 的完整过程,混合 UI 架构的示范(仓库已归档,仍可作参考) |
15. 应用案例(二):行业实践
15.1 国际企业
| 企业 / 框架 | 架构特点 |
|---|---|
| Airbnb Mavericks / Trio [16][17] | Mavericks 是 Airbnb 开源的 MVI 风格框架;Trio 在此基础上面向 Compose,把导航也当作状态的一部分,统一管理 UI 状态和路由状态,简化复杂页面的状态同步。 |
| Square Workflow [18][25] | 采用状态机树模型,把应用逻辑抽象成可组合的工作流,严格控制状态流转。Square 在 POS(收银)等业务中用它满足对状态确定性和可测试性的高要求。 |
| Uber RIBs [22] | Router、Interactor、Builder 架构。由业务逻辑驱动而不是视图驱动,用树形结构实现业务隔离,支撑大型团队协作开发。 |
| Slack Circuit [23] | 面向 Compose 和 Coroutines 的架构框架,提供简洁的 Presenter/UI 契约,代表了声明式 UI 时代的架构方向。 |
15.2 国内大厂
美团技术团队
- 2016 年,MVVM 早期探索 [13]:较早在国内引入 DataBinding 和 MVVM,解决复杂表单、列表的视图更新问题,提高了可维护性。
- 2018 年起,平台化架构 [14][15]:业务膨胀后,美团外卖演进为以组件化、平台化为核心的架构(如 WMRouter 支撑多 App 复用),强调底层能力复用和工程效率。
字节跳动 / 阿里巴巴(⚠️ 基于搜索摘要整理,未核对原文)
- 整体趋势是向 MVI / UDF 范式靠拢:规范数据流向,减少复杂交互下的状态竞争;
- 把 UI 看作某一时刻状态的不可变快照,配合 Diff 优化渲染;
- 架构思想与前端(React/Vue)和跨端框架(Flutter)逐渐趋同。
16. 最佳实践:选型决策与迁移
16.1 新项目架构选型
| 层次 | 推荐 | 说明 |
|---|---|---|
| UI | Jetpack Compose | 声明式 UI |
| VM | MVVM(UDF) | 单向数据流的状态管理 |
| DI | Hilt | 官方推荐的依赖注入方案(不是强制要求) |
| Data | Repository | 单一数据源原则 |
16.2 从 MVP 迁移到 MVVM 的 5 个步骤
- 引入 ViewModel:让 Presenter 持有 ViewModel,或直接把 Presenter 改成 ViewModel,交给它处理生命周期。
- 定义 UiState:把分散的 View 接口回调(如
showLoading、showData)合并成一个不可变的状态数据类。 - 改变状态暴露方式:用 StateFlow(或 LiveData)暴露 UiState,View 层改为订阅状态。
- 逐步移除 Contract:View 接口被状态订阅取代、Presenter 接口被 ViewModel 方法取代后,删除 Contract 类。
- 引入 Repository:把 ViewModel 中获取数据的逻辑移到 Repository,统一管理数据源。
建议按页面或功能模块逐个迁移,每迁移一个就补齐 ViewModel 单元测试,作为回归保障。
17. 最佳实践:技术栈、测试与反模式
17.1 推荐技术栈
| 领域 | 推荐 |
|---|---|
| UI 层 | Jetpack Compose / ViewBinding |
| 状态管理与异步 | StateFlow + Coroutines |
| 依赖注入 | Hilt(基于 Dagger) |
| 数据层 | Repository + Room + Retrofit |
| 导航 | Navigation 3(类型安全) |
17.2 测试策略
| 测试对象 | 方法 |
|---|---|
| ViewModel | 重点断言状态:输入事件后,检查 StateFlow 暴露的状态是否符合预期;可用 Turbine 测试 Flow 的发射序列。 |
| Repository | 用 Fake 实现(官方更推荐)或 MockK 隔离网络、数据库等外部依赖,验证业务逻辑和数据转换。 |
| UI | 用 Compose 语义(Semantics)测试检查 UI 节点的存在、状态和交互;基于 View 的项目继续使用 Espresso。 |
17.3 常见反模式
| 反模式 | 问题 | 正确做法 |
|---|---|---|
| ViewModel 持有 View / Activity Context | ViewModel 比 Activity/Fragment 活得久,会导致内存泄漏 | 只暴露状态;确实需要 Context 时用 Application Context |
| UI 层直接访问数据源 | Activity 直接调用 Room/Retrofit,破坏分层和单向数据流 | 经由 ViewModel → Repository |
| 用 SharedFlow 发送一次性事件 | 配置变更或界面不在前台时,事件可能丢失或被重复处理 | 把事件建模为 UI 状态的一部分 [4] |
| 滥用 GlobalScope | 无法控制协程生命周期,后台任务泄漏 | 使用 viewModelScope / lifecycleScope |
| 多个分散的 LiveData/StateFlow 暴露同一页面状态 | 状态之间不一致,难以追踪 | 合并为单一 UiState |
| UseCase 只做转发 | 增加样板代码,没有实际价值 | 只在逻辑复杂或需要复用时引入 |
18. 总结与展望
- 状态管理思想的进化:架构演进的本质不只是代码结构或分层的变化,而是对“状态”的认识和管理方式在升级:从混乱到有序,从命令式到声明式。
- 清晰管理状态和数据流:架构的价值不在于叫 MVC、MVP、MVVM 还是 MVI,而在于复杂业务中能否提供清晰、可预测的数据流转。
- UDF 与 SSOT:无论采用哪种模式,坚持单向数据流(UDF)和单一数据源(SSOT),都是降低系统复杂度、避免状态不一致的基础。
最终结论:守住核心原则,不盲目追随模式标签,才能保证 Android 应用架构长期可维护、可扩展。
19. 参考资料
参考资料
- developer.android.com - Guide to app architecture
- developer.android.com - Recommendations for Android architecture
- developer.android.com - UI layer
- developer.android.com - UI layer events
- developer.android.com - Domain layer
- developer.android.com - Data layer
- developer.android.com - State hoisting in Compose
- developer.android.com - Dependency injection with Hilt
- github.com - android/nowinandroi…
- github.com - android/architectur…
- github.com - android/sunflower
- blog.cleancoder.com - The Clean Architecture (2012-08-13)
- tech.meituan.com - 如何构建Android MVVM应用框架 (2016-11-11)
- tech.meituan.com - 美团外卖Android平台化架构演进 (2018-03-16)
- tech.meituan.com - WMRouter:美团外卖Android多App复用实践 (2018-09-27)
- medium.com - Airbnb Tech - Introducing Trio: Part I (2024-03)
- github.com - airbnb/mavericks
- square.github.io - Workflow Concepts
- hannesdorfmann.com - Mosby: A Model-View-Presenter library
- proandroiddev.com - Google's UDF vs real world MVI-flavored MVVM
- infoq.com - Android Jetpack Navigation 3 Overview (2025-11)
- github.com - Uber RIBs
- slackhq.github.io - Slack Circuit
- proandroiddev.com - Get your MVP right with Mosby (2017-05-15)
- developer.squareup.com - Why Workflow: Scaling Square Point of Sale