安卓开发架构详解:从 MVC 到 Clean Architecture 的演进之路

举报
yd_217846120 发表于 2026/09/01 08:38:00 2026/09/01
【摘要】 安卓开发架构详解:从 MVC 到 Clean Architecture 的演进之路 前言安卓开发历经十余年发展,架构模式不断演进。从早期被诟病的"上帝类"(God Class)到如今主流的 MVVM、MVI、Clean Architecture,每一次架构升级都是为了解决前一代的痛点。本文将系统梳理安卓开发中常见的架构模式,分析各自的优缺点、适用场景,并给出实战选型建议。 一、架构演进背景...

安卓开发架构详解:从 MVC 到 Clean Architecture 的演进之路

前言

安卓开发历经十余年发展,架构模式不断演进。从早期被诟病的"上帝类"(God Class)到如今主流的 MVVM、MVI、Clean Architecture,每一次架构升级都是为了解决前一代的痛点。本文将系统梳理安卓开发中常见的架构模式,分析各自的优缺点、适用场景,并给出实战选型建议。


一、架构演进背景

1.1 早期安卓开发的困境

在安卓早期,开发者习惯把所有逻辑塞进 ActivityFragment

  • UI 渲染逻辑
  • 业务逻辑
  • 网络请求
  • 数据库操作
  • 状态管理

这种做法导致:

  1. 类爆炸:单个 Activity 动辄数千行,难以维护。
  2. 测试困难:逻辑与 Framework 强耦合,单元测试几乎无法进行。
  3. 复用性差:业务逻辑绑死在特定 UI 上,无法在不同界面复用。
  4. 生命周期陷阱:异步回调持有 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 注意事项

  1. 模块依赖应为 DAG(有向无环图),禁止循环依赖。
  2. 公共能力下沉到 core 模块,业务模块只依赖 core。
  3. 模块边界要清晰,避免"假模块化"(只是分了目录,依赖仍乱)。
  4. 多模块构建会拖慢增量编译,需配合模块隔离测试。

九、架构选型建议

9.1 按项目规模选型

项目规模 推荐架构 理由
Demo / 工具类小应用 MVC 或单 Activity 复杂度低,过度架构反而拖累
中型应用(5-20 页面) MVVM 平衡复杂度与开发效率
大型应用(多业务线) MVVM + 模块化 隔离业务,并行开发
复杂状态应用(播放器/直播) MVI 状态可预测,调试方便
长期维护企业级应用 Clean Architecture + 模块化 可测试、可复用、可替换

9.2 按团队能力选型

  • 团队新手多:MVVM,配合 Jetpack 官方组件,文档丰富。
  • 团队对函数式编程熟悉:MVI,充分发挥单向数据流优势。
  • 团队有跨端复用需求:Clean Architecture,Domain 层可共享给 iOS/后端。

9.3 通用原则

  1. 不要过度设计:架构服务于业务,而非反过来。
  2. 保持依赖方向一致:UI → ViewModel → UseCase → Repository → DataSource。
  3. 状态集中管理:避免散落在多个 LiveData/Flow 中。
  4. 不可变数据优先:用 data class + copy() 而非可变字段。
  5. 测试驱动:架构是否合理,看 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:分层最彻底,适合大型长期项目。
  • 模块化:项目变大后的必经之路。

没有"最好"的架构,只有"最适合当前项目和团队"的架构。理解每种架构解决的核心问题,根据业务规模、团队能力、维护周期做出权衡,才是架构选型的正确姿势。

架构不是一次性决策,而是随业务演进的持续重构过程。保持代码可测试、依赖方向清晰、状态可预测,比死守某种模式更重要。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。