安卓开发架构详解:从 MVC 到 Clean Architecture 的演进之路
安卓开发架构详解:从 MVC 到 Clean Architecture 的演进之路
前言
安卓开发历经十余年发展,架构模式不断演进。从早期被诟病的"上帝类"(God Class)到如今主流的 MVVM、MVI、Clean Architecture,每一次架构升级都是为了解决前一代的痛点。本文将系统梳理安卓开发中常见的架构模式,分析各自的优缺点、适用场景,并给出实战选型建议。
一、架构演进背景
1.1 早期安卓开发的困境
在安卓早期,开发者习惯把所有逻辑塞进 Activity 或 Fragment:
- UI 渲染逻辑
- 业务逻辑
- 网络请求
- 数据库操作
- 状态管理
这种做法导致:
- 类爆炸:单个 Activity 动辄数千行,难以维护。
- 测试困难:逻辑与 Framework 强耦合,单元测试几乎无法进行。
- 复用性差:业务逻辑绑死在特定 UI 上,无法在不同界面复用。
- 生命周期陷阱:异步回调持有 Activity 引用,极易引发内存泄漏。
1.2 架构演进的核心目标
| 目标 | 说明 |
|---|---|
| 关注点分离 | UI、业务、数据各司其职 |
| 可测试性 | 业务逻辑可脱离 UI 独立测试 |
| 可复用性 | 业务逻辑可在多界面复用 |
| 生命周期安全 | 异步操作不泄漏 Activity/Fragment |
| 响应式数据流 | 数据变化自动驱动 UI 更新 |
二、MVC 架构
2.1 结构
View (XML 布局)
↑
Controller (Activity/Fragment) → Model (数据模型)
在安卓中,MVC 的落地方式通常是:
- Model:JavaBean、数据实体类。
- View:XML 布局文件。
- Controller:Activity / Fragment,既处理 UI 事件,又处理业务逻辑。
2.2 示例代码
class UserController : AppCompatActivity() {
private lateinit var userNameText: TextView
private lateinit var loadButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
userNameText = findViewById(R.id.tvUserName)
loadButton = findViewById(R.id.btnLoad)
loadButton.setOnClickListener {
// 业务逻辑直接写在 Controller 中
Thread {
val user = fetchUserFromNetwork()
runOnUiThread {
userNameText.text = user.name
}
}.start()
}
}
private fun fetchUserFromNetwork(): User {
// 模拟网络请求
return User("张三", 25)
}
}
2.3 优缺点
优点:
- 简单直观,上手快。
- 适合小型 Demo 或一次性脚本。
缺点:
- Activity 承担过多职责,容易变成上帝类。
- View 与 Controller 强耦合,难以单元测试。
- 业务逻辑无法复用。
结论:MVC 在安卓中几乎是被抛弃的模式,仅适合极小规模项目。
三、MVP 架构
3.1 结构
View (Activity/Fragment + XML)
↕ 接口
Presenter (业务逻辑)
↕
Model (数据源)
MVP 的核心思想是:通过接口将 View 与 Presenter 解耦,Presenter 持有 View 的接口引用,业务逻辑全部放在 Presenter 中。
3.2 示例代码
View 接口:
interface UserContract {
interface View {
fun showUserName(name: String)
fun showLoading()
fun hideLoading()
fun showError(message: String)
}
interface Presenter {
fun loadUser()
fun onDestroy()
}
}
Presenter 实现:
class UserPresenter(
private val view: UserContract.View,
private val userRepository: UserRepository
) : UserContract.Presenter {
override fun loadUser() {
view.showLoading()
userRepository.getUser(object : Callback<User> {
override fun onSuccess(user: User) {
view.hideLoading()
view.showUserName(user.name)
}
override fun onError(error: Throwable) {
view.hideLoading()
view.showError(error.message ?: "未知错误")
}
})
}
override fun onDestroy() {
// 清理资源,避免内存泄漏
}
}
Activity 作为 View:
class UserActivity : AppCompatActivity(), UserContract.View {
private lateinit var presenter: UserContract.Presenter
private lateinit var userNameText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
userNameText = findViewById(R.id.tvUserName)
presenter = UserPresenter(this, UserRepository())
presenter.loadUser()
}
override fun showUserName(name: String) {
userNameText.text = name
}
override fun showLoading() {
// 显示加载框
}
override fun hideLoading() {
// 隐藏加载框
}
override fun showError(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
override fun onDestroy() {
super.onDestroy()
presenter.onDestroy()
}
}
3.3 优缺点
优点:
- Presenter 与 View 通过接口解耦,Presenter 可独立单元测试。
- 业务逻辑集中在 Presenter,复用性提升。
- 职责划分清晰。
缺点:
- 接口爆炸:每个页面都要定义 View 和 Presenter 接口。
- Presenter 持有 View 引用,生命周期管理繁琐,容易内存泄漏。
- 数据流是命令式的,UI 状态同步需要手动维护。
- 不支持响应式数据流,状态变化需要主动调用 View 方法刷新。
四、MVVM 架构
4.1 结构
View (Activity/Fragment + XML)
↕ 观察者
ViewModel (状态 + 业务逻辑)
↕
Model (Repository → Network / DB)
MVVM 是谷歌官方推荐的架构(见 Jetpack Guide)。核心思想是:
- ViewModel 持有 UI 状态,不持有 View 引用。
- View 观察 ViewModel 的状态变化,自动更新 UI。
- 数据绑定是单向的、响应式的。
4.2 关键组件
| 组件 | 职责 |
|---|---|
| ViewModel | 持有 UI 状态,处理业务逻辑,生命周期安全 |
| LiveData / StateFlow | 持有可观察数据,自动管理生命周期 |
| Repository | 统一数据源(网络 + 本地) |
| DataBinding / ViewBinding | 减少 findViewById,绑定 UI 与数据 |
4.3 示例代码
ViewModel:
class UserViewModel(
private val userRepository: UserRepository
) : ViewModel() {
// 使用 StateFlow 暴露不可变状态
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser() {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
try {
val user = userRepository.getUser()
_uiState.value = UserUiState.Success(user)
} catch (e: Exception) {
_uiState.value = UserUiState.Error(e.message ?: "未知错误")
}
}
}
}
sealed class UserUiState {
object Loading : UserUiState()
data class Success(val user: User) : UserUiState()
data class Error(val message: String) : UserUiState()
}
Activity:
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels {
UserViewModelFactory(UserRepository())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityUserBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel.loadUser()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is UserUiState.Loading -> {
binding.progressBar.visibility = View.VISIBLE
}
is UserUiState.Success -> {
binding.progressBar.visibility = View.GONE
binding.tvUserName.text = state.user.name
}
is UserUiState.Error -> {
binding.progressBar.visibility = View.GONE
Toast.makeText(this@UserActivity, state.message, Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
}
4.4 优缺点
优点:
- ViewModel 不持有 View 引用,天然规避内存泄漏。
- 生命周期安全:配置变更(如旋转屏幕)后 ViewModel 自动恢复。
- 响应式数据流,UI 自动跟随状态变化。
- 单元测试友好:ViewModel 可独立测试。
- 谷 RxJava 结合后非常适合复杂异步数据流编排。
缺点:
- StateFlow/LiveData 的状态管理在复杂场景下仍可能混乱(多状态交织)。
- 双向数据绑定(DataBinding)容易让 XML 变得难以追踪。
- 对初学者有一定学习曲线。
结论:MVVM 是当前安卓开发的事实标准,绝大多数中大型项目首选。
五、MVI 架构
5.1 核心思想
MVI(Model-View-Intent)强调单向数据流(Unidirectional Data Flow, UDF):
User Intent → ViewModel → New State → View
↑ |
└──────────────────────────────────────┘
- Intent:不是
android.content.Intent,而是用户的"意图"(事件),如点击按钮、下拉刷新。 - State:UI 的完整状态快照,用
sealed class描述。 - View:根据 State 渲染 UI,将用户操作转化为 Intent 发给 ViewModel。
5.2 示例代码
State 定义:
data class UserState(
val isLoading: Boolean = false,
val userName: String = "",
val errorMessage: String? = null
)
Intent 定义:
sealed class UserIntent {
object LoadUser : UserIntent()
object RefreshUser : UserIntent()
data class UpdateUserName(val name: String) : UserIntent()
}
ViewModel:
class UserMviViewModel(
private val userRepository: UserRepository
) : ViewModel() {
private val _state = MutableStateFlow(UserState())
val state: StateFlow<UserState> = _state.asStateFlow()
fun handleIntent(intent: UserIntent) {
when (intent) {
is UserIntent.LoadUser -> loadUser()
is UserIntent.RefreshUser -> loadUser()
is UserIntent.UpdateUserName -> {
_state.update { it.copy(userName = intent.name) }
}
}
}
private fun loadUser() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true, errorMessage = null) }
try {
val user = userRepository.getUser()
_state.update { it.copy(isLoading = false, userName = user.name) }
} catch (e: Exception) {
_state.update { it.copy(isLoading = false, errorMessage = e.message) }
}
}
}
}
Activity:
class UserMviActivity : AppCompatActivity() {
private val viewModel: UserMviViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityUserBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnLoad.setOnClickListener {
viewModel.handleIntent(UserIntent.LoadUser)
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.state.collect { render(it) }
}
}
}
private fun render(state: UserState) {
findViewById<ProgressBar>(R.id.progressBar).visibility =
if (state.isLoading) View.VISIBLE else View.GONE
findViewById<TextView>(R.id.tvUserName).text = state.userName
state.errorMessage?.let {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}
}
}
5.3 优缺点
优点:
- 单向数据流,状态可预测、可追溯,调试方便。
- State 是不可变快照,天然避免并发修改问题。
- 易于做时间旅行调试(Time-Travel Debugging)。
- 对复杂状态机场景(如多步骤表单、播放器)非常合适。
缺点:
- State 对象每次更新都要新建,高频更新场景下有一定性能开销。
- Intent 类型膨胀,简单页面也需定义较多样板代码。
- 学习曲线最陡。
结论:MVI 适合状态复杂、对可预测性要求高的场景,如直播、播放器、复杂表单。
六、Clean Architecture
6.1 分层
Clean Architecture 由 Uncle Bob 提出,在安卓中通常分为三层:
┌─────────────────────────────────┐
│ Presentation (UI + ViewModel) │ ← 外层
├─────────────────────────────────┤
│ Domain (UseCase + Entity) │ ← 中层(纯 Kotlin/Java,无安卓依赖)
├─────────────────────────────────┤
│ Data (Repository + API + DB) │ ← 外层
└─────────────────────────────────┘
依赖规则:外层依赖内层,内层不依赖外层。Domain 层是纯 Kotlin 模块,不依赖安卓 Framework。
6.2 各层职责
| 层 | 职责 | 关键类 |
|---|---|---|
| Presentation | UI 渲染、用户交互 | Activity、Fragment、ViewModel |
| Domain | 业务规则、用例编排 | UseCase、Entity、Repository 接口 |
| Data | 数据源实现 | RepositoryImpl、Retrofit API、Room DAO |
6.3 示例代码
Entity:
data class User(
val id: String,
val name: String,
val age: Int
)
Repository 接口(Domain 层):
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun saveUser(user: User)
}
UseCase(Domain 层):
class GetUserUseCase(
private val userRepository: UserRepository
) {
suspend operator fun invoke(id: String): User {
return userRepository.getUser(id)
}
}
Repository 实现(Data 层):
class UserRepositoryImpl(
private val userApi: UserApi,
private val userDao: UserDao
) : UserRepository {
override suspend fun getUser(id: String): User {
// 先查本地缓存
val local = userDao.getUser(id)
if (local != null) return local
// 没有则请求网络
val remote = userApi.getUser(id).toEntity()
userDao.insert(remote)
return remote
}
override suspend fun saveUser(user: User) {
userDao.insert(user)
}
}
ViewModel(Presentation 层):
class UserCleanViewModel(
private val getUserUseCase: GetUserUseCase
) : ViewModel() {
private val _state = MutableStateFlow<UserState>(UserState())
val state: StateFlow<UserState> = _state.asStateFlow()
fun loadUser(id: String) {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
try {
val user = getUserUseCase(id)
_state.update { it.copy(isLoading = false, userName = user.name) }
} catch (e: Exception) {
_state.update { it.copy(isLoading = false, errorMessage = e.message) }
}
}
}
}
6.4 优缺点
优点:
- Domain 层完全独立于安卓,可被多平台复用。
- 分层清晰,团队协作分工明确。
- 极强的可测试性:每层都可独立 mock 测试。
- 便于替换数据源(如换网络库、换数据库)。
缺点:
- 模块多、模板代码多,小型项目过度设计。
- 学习成本高,需要理解依赖反转、接口隔离等原则。
- 数据在层间传递需要多次映射(Entity ↔ DTO ↔ UI Model),有一定开销。
结论:Clean Architecture 适合长期维护、多人协作、对可测试性要求高的大型项目。
七、结合 RxJava 的响应式架构
在偏好 RxJava 的项目中,可以用 RxJava 替代 Coroutine + Flow,构建响应式数据流。
7.1 Repository 层返回 Observable
class RxUserRepository(
private val userApi: UserApi,
private val userDao: UserDao
) : UserRepository {
override fun getUser(id: String): Observable<User> {
return userDao.getUser(id)
.onErrorResumeNext { userApi.getUser(id).toEntity() }
.doOnNext { userDao.insert(it) }
}
}
7.2 ViewModel 用 Observable 暴露状态
class RxUserViewModel(
private val getUserUseCase: GetUserUseCase
) : ViewModel() {
private val stateSubject = BehaviorSubject.createDefault(UserState())
val state: Observable<UserState> = stateSubject.hide()
fun loadUser(id: String) {
getUserUseCase(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { stateSubject.onNext(stateSubject.value!!.copy(isLoading = true)) }
.subscribe(
{ user ->
stateSubject.onNext(stateSubject.value!!.copy(
isLoading = false,
userName = user.name
))
},
{ error ->
stateSubject.onNext(stateSubject.value!!.copy(
isLoading = false,
errorMessage = error.message
))
}
)
.addTo(compositeDisposable)
}
private val compositeDisposable = CompositeDisposable()
override fun onCleared() {
compositeDisposable.dispose()
super.onCleared()
}
}
7.3 RxJava vs Kotlin Flow 选型
| 维度 | RxJava | Kotlin Flow |
|---|---|---|
| 学习曲线 | 陡(操作符众多) | 平缓(协程基础上) |
| 背压支持 | 完善(Flowable) | 完善(Channel/Flow) |
| 生态成熟度 | 极高,多年沉淀 | 快速增长中 |
| 与协程集成 | 需桥接 | 原生支持 |
| 包体积 | 较大 | 较小(协程库) |
| 调试体验 | 较难(链式调用) | 较好(协程栈) |
建议:新项目优先 Kotlin Coroutine + Flow;存量 RxJava 项目可逐步迁移,或继续沿用 RxJava。
八、模块化架构
随着项目变大,单一 module 难以维护,需要按业务拆分模块。
8.1 典型分层
:app // 壳工程,负责组装
:core-ui // 通用 UI 组件
:core-network // 网络层
:core-db // 数据库层
:core-common // 通用工具
:feature-user // 用户模块
:feature-order // 订单模块
:feature-payment // 支付模块
8.2 模块间通信
- 同进程:通过接口 + 依赖注入(Hilt/Dagger)。
- 跨进程:通过 AIDL、ContentProvider 或事件总线。
- 解耦通信:可使用 Service Locator 模式或路由框架。
8.3 注意事项
- 模块依赖应为 DAG(有向无环图),禁止循环依赖。
- 公共能力下沉到
core模块,业务模块只依赖 core。 - 模块边界要清晰,避免"假模块化"(只是分了目录,依赖仍乱)。
- 多模块构建会拖慢增量编译,需配合模块隔离测试。
九、架构选型建议
9.1 按项目规模选型
| 项目规模 | 推荐架构 | 理由 |
|---|---|---|
| Demo / 工具类小应用 | MVC 或单 Activity | 复杂度低,过度架构反而拖累 |
| 中型应用(5-20 页面) | MVVM | 平衡复杂度与开发效率 |
| 大型应用(多业务线) | MVVM + 模块化 | 隔离业务,并行开发 |
| 复杂状态应用(播放器/直播) | MVI | 状态可预测,调试方便 |
| 长期维护企业级应用 | Clean Architecture + 模块化 | 可测试、可复用、可替换 |
9.2 按团队能力选型
- 团队新手多:MVVM,配合 Jetpack 官方组件,文档丰富。
- 团队对函数式编程熟悉:MVI,充分发挥单向数据流优势。
- 团队有跨端复用需求:Clean Architecture,Domain 层可共享给 iOS/后端。
9.3 通用原则
- 不要过度设计:架构服务于业务,而非反过来。
- 保持依赖方向一致:UI → ViewModel → UseCase → Repository → DataSource。
- 状态集中管理:避免散落在多个 LiveData/Flow 中。
- 不可变数据优先:用
data class+copy()而非可变字段。 - 测试驱动:架构是否合理,看 ViewModel/UseCase 能否脱离 UI 单元测试。
十、常见反模式
10.1 ViewModel 中直接操作 View
// 反模式
class BadViewModel : ViewModel() {
fun showError(textView: TextView, msg: String) {
textView.text = msg // ViewModel 不应持有 View
}
}
正确做法:通过 StateFlow 暴露错误状态,由 View 自行渲染。
10.2 Repository 直接返回框架对象
// 反模式
interface BadRepository {
fun getUser(): Response<ResponseBody> // 暴露了 OkHttp/Retrofit 细节
}
正确做法:返回 Domain Entity,数据层做映射。
10.3 在 Activity 中直接发网络请求
// 反模式
class BadActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread {
val result = URL("...").readText() // 业务逻辑泄漏到 UI 层
}.start()
}
}
正确做法:网络请求下沉到 Repository,由 ViewModel 调度。
10.4 用全局单例传递数据
// 反模式
object GlobalData {
var currentUser: User? = null
}
风险:生命周期不可控,多进程/多实例下数据不一致,难以测试。
正确做法:通过 ViewModel、SavedStateHandle 或 Repository 持久化。
十一、测试策略
架构的好坏最终体现在可测试性上。
11.1 分层测试
| 层 | 测试类型 | 工具 |
|---|---|---|
| ViewModel | 单元测试 | JUnit + Mockito + Turbine(Flow 测试) |
| UseCase | 单元测试 | JUnit + MockK |
| Repository | 集成测试 | Robolectric + Room in-memory |
| UI | 仪器测试 | Espresso / Compose UI Test |
11.2 ViewModel 测试示例
class UserViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
private val userRepository = mockk<UserRepository>()
private lateinit var viewModel: UserViewModel
@Before
fun setup() {
viewModel = UserViewModel(userRepository)
}
@Test
fun `loadUser success updates state to Success`() = runTest {
// Arrange
coEvery { userRepository.getUser() } returns User("1", "张三", 25)
// Act
viewModel.loadUser()
// Assert
viewModel.uiState.test {
assertEquals(UserUiState.Loading, awaitItem())
assertEquals(UserUiState.Success(User("1", "张三", 25)), awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
}
十二、总结
安卓架构演进的本质,是不断把业务逻辑从 UI 中剥离出来,让代码更可测、更可复用、更可维护。
- MVC:历史包袱,已不推荐。
- MVP:解耦了 View 和业务,但接口爆炸、生命周期管理繁琐。
- MVVM:当前主流,响应式 + 生命周期安全,配合 Jetpack 体验最佳。
- MVI:状态可预测性最强,适合复杂状态场景。
- Clean Architecture:分层最彻底,适合大型长期项目。
- 模块化:项目变大后的必经之路。
没有"最好"的架构,只有"最适合当前项目和团队"的架构。理解每种架构解决的核心问题,根据业务规模、团队能力、维护周期做出权衡,才是架构选型的正确姿势。
架构不是一次性决策,而是随业务演进的持续重构过程。保持代码可测试、依赖方向清晰、状态可预测,比死守某种模式更重要。
- 点赞
- 收藏
- 关注作者
评论(0)