Android 项目架构设计:从 MVC 到 MVI 的演进与实践
引言在 Android 应用开发中一个清晰、可维护、可测试的架构设计是项目成功的关键。随着应用复杂度的提升和团队规模的扩大选择合适的架构模式变得至关重要。本文将带你深入浅出地了解 Android 项目架构设计的演进历程、主流模式对比以及如何在实际项目中落地实践。1. 架构设计的重要性为什么我们需要关注架构代码可维护性清晰的架构让代码结构一目了然新成员能快速上手老成员能高效定位问题。可测试性良好的架构天然支持单元测试、集成测试确保代码质量。团队协作模块化的设计允许不同开发者或团队并行开发减少冲突。可扩展性当业务需求变化或增加新功能时良好的架构能最小化改动成本实现平滑扩展。单一职责每个组件、类甚至方法都有明确的职责避免出现“上帝类”或“面条式代码”。一个没有良好架构的应用随着时间推移往往会变成难以维护的“遗留代码”任何改动都可能引发意想不到的 Bug。2. 经典架构模式演进Android 架构并非一成不变它随着开发理念和工具链的进步而不断演进。2.1 MVC (Model-View-Controller)模型 (Model)负责数据和业务逻辑。视图 (View)负责 UI 展示在 Android 中通常是Activity/Fragment和 XML 布局。控制器 (Controller)负责处理用户输入更新模型和视图。在 Android 早期Activity/Fragment常常身兼 View 和 Controller 两职。问题Activity/Fragment过于臃肿承担了太多职责生命周期、UI 更新、业务逻辑、数据获取导致难以测试和维护也就是常说的“上帝对象”。2.2 MVP (Model-View-Presenter)为了解决 MVC 中 View 和 Controller 耦合过紧的问题MVP 模式被引入。模型 (Model)不变负责数据和业务逻辑。视图 (View)变为被动接口只定义 UI 更新的方法。Activity/Fragment实现此接口。表示层 (Presenter)作为 View 和 Model 的中间人持有 View 的引用从 Model 获取数据处理业务逻辑并调用 View 接口的方法来更新 UI。优点将 UI 逻辑从Activity/Fragment中抽离使其变得更薄便于单元测试 Presenter。缺点Presenter 持有 View 的强引用需要小心处理生命周期避免内存泄漏。同时随着业务复杂Presenter 也可能变得庞大。2.3 MVVM (Model-View-ViewModel)借助 Jetpack 组件特别是LiveData和DataBinding/ViewBindingMVVM 成为 Android 官方推荐架构。模型 (Model)负责数据和业务逻辑。视图 (View)Activity/Fragment负责设置 UI 和观察 ViewModel 的数据变化。视图模型 (ViewModel)持有 UI 相关的数据并通过LiveData/StateFlow暴露给 View。它不持有 View 的引用因此与生命周期无关。优点数据驱动 UIView 订阅 ViewModel 的数据流数据变化自动驱动 UI 更新。生命周期感知ViewModel和LiveData自动管理生命周期避免内存泄漏。更好的关注点分离View 只负责展示和用户交互ViewModel 负责准备展示数据。// 一个简单的 ViewModel 示例classUserViewModel(privatevaluserRepository:UserRepository):ViewModel(){privateval_userNameMutableLiveDataString()valuserName:LiveDataString_userNamefunloadUser(userId:String){viewModelScope.launch{valuseruserRepository.getUser(userId)_userName.valueuser.name}}}2.4 MVI (Model-View-Intent)MVI 是一种响应式架构强调单向数据流和状态不可变性。模型 (Model)代表状态 (State)。UI 是所有状态的函数。视图 (View)渲染状态并将用户输入转换为意图 (Intent)发送。意图 (Intent)代表用户或系统发起的动作如按钮点击、初始化。处理器通常由 ViewModel 担任接收 Intent处理业务逻辑并生成新的 State。优点单向数据流数据流向清晰可预测便于调试和追溯状态变化。状态不可变任何时候 State 都是确定的避免了因状态共享和异步修改带来的竞态问题。易于测试业务逻辑集中在处理 Intent 和生成新 State 的过程中易于进行单元测试。// MVI 状态与意图示例dataclassLoginState(valisLoading:Booleanfalse,valisSuccess:Booleanfalse,valerrorMessage:String?null)sealedclassLoginIntent{objectInit:LoginIntent()dataclassLogin(valusername:String,valpassword:String):LoginIntent()objectDismissError:LoginIntent()}3. 现代分层架构设计单一的 MV* 模式不足以支撑大型复杂应用。现代 Android 项目通常采用清晰的分层架构结合 MVVM 或 MVI 作为表现层模式。3.1 典型分层一个健壮的分层架构通常包含以下层次渲染错误:Mermaid 渲染失败: Parse error on line 2: ... subgraph A[表现层 (Presentation Layer) ----------------------^ Expecting SQE, DOUBLECIRCLEEND, PE, -), STADIUMEND, SUBROUTINEEND, PIPE, CYLINDEREND, DIAMOND_STOP, TAGEND, TRAPEND, INVTRAPEND, UNICODE_TEXT, TEXT, TAGSTART, got PS表现层 (Presentation Layer)组件Activity,Fragment,ViewModel,ComposeUI。职责处理 UI 展示和用户交互。使用 MVVM 或 MVI 模式。依赖领域层Use Cases。领域层 (Domain Layer)组件Use Cases (Interactors), Repository Interfaces。职责封装核心、独立的业务规则。它是应用中最稳定的一层不应依赖 Android SDK 或任何框架。依赖无纯 Kotlin/Java 模块。数据层 (Data Layer)组件Repository Implementations, Data Sources (Local/Remote), Data Models。职责管理应用数据提供统一的数据访问入口。实现领域层定义的 Repository 接口。依赖领域层接口以及各种 SDK如 Room, Retrofit。3.2 依赖规则关键原则依赖方向永远指向抽象接口并且向内指向核心领域层。表现层依赖领域层的 Use Cases。数据层实现领域层定义的 Repository 接口。领域层不依赖任何其他层保持纯净。4. 组件化与模块化对于大型团队和项目单一的 App 模块会带来编译慢、职责模糊、团队协作冲突等问题。组件化/模块化是解决方案。4.1 概念模块化 (Modularization)将代码拆分为多个 Gradle 模块:core:network,:feature:login。每个模块可以独立编译明确依赖关系。组件化 (Componentization)在模块化的基础上强调业务功能的独立性和可复用性。每个业务组件如:feature:home可以独立开发、测试甚至理论上能独立运行。4.2 常见模块划分app/ (主App模块负责组装) ├── feature/ (功能模块) │ ├── login/ │ ├── home/ │ └── profile/ ├── core/ (核心基础模块) │ ├── network/ │ ├── database/ │ ├── common/ (通用扩展、工具) │ └── design/ (UI组件、主题) └── domain/ (领域模块纯Kotlin) ├── model/ └── repository/4.3 组件间通信页面跳转使用 Deep Link 或路由框架如 App Startup 自研路由或第三方库。数据传递通过接口暴露服务。可以使用 Service Locator 模式或依赖注入框架如 Hilt/Dagger来管理组件间的依赖。避免循环依赖通过抽取公共接口到:core:api模块等方式解决。5. 实战构建一个组件化 MVVM 项目假设我们要构建一个简单的用户信息展示应用。5.1 项目结构MyApp/ ├── app/ (空壳负责依赖注入和组装) ├── core/ │ ├── network/ (Retrofit 配置) │ ├── database/ (Room 配置) │ └── common/ ├── domain/ │ ├── model/ (User) │ └── repository/ (UserRepository 接口) ├── data/ │ └── repository/ (UserRepository 实现) └── feature/ └── userprofile/ ├── di/ (该功能的 Hilt Module) ├── presentation/ (Activity, ViewModel, State) └── UserProfileFeature.kt5.2 关键代码示例1. Domain Layer (纯 Kotlin)// domain/model/User.ktdataclassUser(valid:String,valname:String,valemail:String)// domain/repository/UserRepository.kt (接口)interfaceUserRepository{suspendfungetUser(id:String):User}2. Data Layer (实现接口)// data/repository/UserRepositoryImpl.ktclassUserRepositoryImplInjectconstructor(privatevaluserService:UserService,// Retrofit 接口privatevaluserDao:UserDao// Room Dao):UserRepository{overridesuspendfungetUser(id:String):User{// 业务逻辑优先缓存网络获取后更新缓存returnuserDao.getUser(id)?:fetchFromNetwork(id)}privatesuspendfunfetchFromNetwork(id:String):User{...}}3. Presentation Layer (MVVM with State)// feature/userprofile/presentation/UserProfileViewModel.ktHiltViewModelclassUserProfileViewModelInjectconstructor(privatevalgetUserUseCase:GetUserUseCase// UseCase 封装了业务逻辑):ViewModel(){privateval_uiStateMutableStateFlow(UserProfileState())valuiState:StateFlowUserProfileState_uiState.asStateFlow()funloadUser(userId:String){viewModelScope.launch{_uiState.update{it.copy(isLoadingtrue)}try{valusergetUserUseCase(userId)_uiState.update{it.copy(isLoadingfalse,useruser)}}catch(e:Exception){_uiState.update{it.copy(isLoadingfalse,errore.message)}}}}}// feature/userprofile/presentation/UserProfileState.ktdataclassUserProfileState(valisLoading:Booleanfalse,valuser:User?null,valerror:String?null)6. 架构选择建议与总结新手或小型项目从MVVM开始。它学习曲线平缓有强大的官方组件ViewModel, LiveData支持能解决大部分生命周期和数据绑定问题。中型至大型项目采用分层架构 MVVM/MVI。引入清晰的领域层和数据层为模块化打下基础。如果状态管理变得复杂可以考虑引入 MVI 来获得更可预测的数据流。超大型团队项目必须推进组件化/模块化。结合单一职责和依赖倒置原则使用 Hilt/Dagger 进行依赖注入管理模块间通信。记住没有“银弹”架构。最好的架构是适合你的团队规模、项目复杂度和迭代速度的架构。核心目标是控制复杂度提升代码的可读性、可测试性和可维护性。架构设计是一个持续演进的过程随着 Kotlin Multiplatform、Jetpack Compose 等新技术的普及未来的架构模式也必将有新的发展。保持学习灵活应用。

相关新闻

【办公类-16-02】20220923“2022上学期 大班运动场地分配表1-跳过节日循环排序”(python 排班表系列)

【办公类-16-02】20220923“2022上学期 大班运动场地分配表1-跳过节日循环排序”(python 排班表系列)

样例展示:跳过节日的运动场地循环排序表(8个班级8组内容) 背景需求: 复工复学后,周计划做起来!每周的运动场地安排照旧…… 存在问题(一): 8个大班,靠着人工排序,每周都要推算一次这个规则,推演麻烦,且容易出错 (1)每周都要打开前一周教案看写到哪一个场地, (…

2026/10/12 14:44:33 阅读更多 →
k7沙箱创建与配置:从YAML文件到高级安全策略

k7沙箱创建与配置:从YAML文件到高级安全策略

k7沙箱创建与配置:从YAML文件到高级安全策略 【免费下载链接】k7 Your own self-hosted infra for lightweight VM sandboxes to safely execute untrusted code. CLI, API, Python SDK. ⭐ Star it if you like it! ⭐ 项目地址: https://gitcode.com/gh_mirrors…

2026/10/12 5:25:43 阅读更多 →
如何轻松下载国家中小学智慧教育平台电子课本:tchMaterial-parser的完整指南

如何轻松下载国家中小学智慧教育平台电子课本:tchMaterial-parser的完整指南

如何轻松下载国家中小学智慧教育平台电子课本:tchMaterial-parser的完整指南 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具,帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载,让您更方便地获取…

2026/10/12 21:18:08 阅读更多 →

最新新闻

VCU整车控制器Simulink源码深度拆解:架构、算法与工程落地

VCU整车控制器Simulink源码深度拆解:架构、算法与工程落地

做新能源三电这行,很多人对“VCU整车控制器”是既熟悉又陌生。熟悉是因为它几乎是纯电、混动、增程构型里绕不开的顶层控制器,陌生是因为真正的工程源码很难见到,网上的资料要么是论文里的原理框图,要么是教学级别的简化模型。最近…

2026/10/13 6:23:46 阅读更多 →
一张图15秒带货视频:Claude Code本地合成全链路实战

一张图15秒带货视频:Claude Code本地合成全链路实战

1. 一张随手拍凭什么能撑起一条带货视频很多人第一次听到"一张照片做15秒带货视频"的反应是:这不就是拿张图配个音乐,然后硬凑时长吗?我一开始也这么想,直到自己真正跑通整条链路,才发现这里面能玩的花样比想…

2026/10/13 6:23:46 阅读更多 →
agent-skills 实战:构建大模型智能体的技能注册、调用与编排体系

agent-skills 实战:构建大模型智能体的技能注册、调用与编排体系

最近我在折腾智能体开发时,被一个高频问题反复卡住:模型本身挺聪明,一让它干具体活儿就各种掉链子。要么不知道该调用哪个工具,要么把参数传得乱七八糟,要么只回复“我可以帮你处理”然后就没有然后了。后来我意识到&a…

2026/10/13 6:23:46 阅读更多 →
Leetcode hot100 颜色分类【中等】

Leetcode hot100 颜色分类【中等】

看懂题目后,直觉是 —— new一个一样大小的数组,遍历一遍原数组,数出来0、1、2的个数,然后按照个数直接写入新数组。但题目要求在原地弄,也就是不能通过我们这样拷贝的方式。并且进阶要求只遍历一遍。空间限制我们&…

2026/10/13 6:23:46 阅读更多 →
图像预处理实战指南:从几何变换到工程落地的全流程避坑

图像预处理实战指南:从几何变换到工程落地的全流程避坑

图像预处理这个环节,说它是整个视觉任务里最不起眼、却最容易决定成败的一步,一点都不夸张。我做过不少图像相关的项目,从分类、检测到分割,也帮人排查过不少"模型明明没问题但效果就是上不去"的案例,最后追…

2026/10/13 6:23:46 阅读更多 →
多智能体协作系统实战:架构设计与落地经验

多智能体协作系统实战:架构设计与落地经验

1. 从单兵作战到团队协同:为什么我们需要重新思考开发模式做过几年开发的人都有一个共同感受:项目越做越大,代码越写越多,但真正让人头疼的往往不是某个技术难点,而是协作本身。需求在变、接口在变、文档永远滞后于代码…

2026/10/13 6:22:46 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →