Android 项目架构设计:从 MVC 到 MVI 的演进与实践

发布时间:2026/7/26 17:36:05
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 等新技术的普及未来的架构模式也必将有新的发展。保持学习灵活应用。