真正的问题
在没有引入架构时,我们的 Activity 或 Fragment 非常臃肿:既要负责展示 View,又要负责响应点击事件,还需要去负责网络请求,处理业务逻辑,管理数据状态...
这导致了高耦合性,这种代码几乎无法测试且难以维护。
所以我们引入了架构模式,就是为了拆开 Activity 或 Fragment。
MVC 和 MVP 的关系
首先澄清一个误区:MVP 并不是 MVC 的改进版本,只是解决同一个问题的两种不同的拆分思路。
我们通常认为原生 Android 就是 MVC,其中 Activity 是 Controller,XML 布局是 View,
kotlin
// Model: 业务逻辑和数据
class CounterModel {
private var count = 0
fun increment() {
count++
}
fun getCount(): Int = count
}
class MainActivity : AppCompatActivity() {
private val model = CounterModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.view_counter_content)
val countTextView = findViewById<TextView>(R.id.count_text_view)
val incrementButon = findViewById<Button>(R.id.increment_button)
incrementButon.setOnClickListener {
model.increment()
countTextView.text = model.getCount().toString()
}
}
}
view_counter_content.xml 文件布局代码:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/count_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="24sp" />
<Button
android:id="@+id/increment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Increment" />
</LinearLayout>
但 XML(View)只负责布局,肩负的职责太少,显示都是由 Activity(Controller)来完成的,所以 C、V 还是耦合在了一起,我们可以称其为 "M-VC" 架构。
MVC 拆 View:解耦视图
我们可以封装一个自定义 View,让它自己在管理内部的 UI 和事件,对外暴露接口和回调。
kotlin
// Model: 业务逻辑和数据
class CounterModel {
private var count = 0
fun increment() {
count++
}
fun getCount(): Int = count
}
// 管理自己的子视图和 UI 事件
class CounterView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
private val countTextView: TextView
private val incrementButon: Button
private var onIncrementListener: (() -> Unit)? = null
init {
// 加载布局
inflate(context, R.layout.view_counter_content, this)
countTextView = findViewById(R.id.count_text_view)
incrementButon = findViewById(R.id.increment_button)
incrementButon.setOnClickListener {
// 通知 Controller 发生了点击事件
onIncrementListener?.invoke()
}
}
// 让 Controller 更新 UI
fun setCount(count: Int) {
countTextView.text = count.toString()
}
// 让 Controller 监听 UI 事件 (命名已统一)
fun setOnIncrementListener(listener: () -> Unit) {
this.onIncrementListener = listener
}
}
// Controller: Activity,负责调度
class MVCActivity : AppCompatActivity() {
private val model = CounterModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mvc)
val counterView = findViewById<CounterView>(R.id.counter_view)
// Controller 初始加载数据
counterView.setCount(model.getCount())
// Controller 监听 UI 事件
counterView.setOnIncrementListener {
// 事件触发,更新 Model
model.increment()
// 使用 Model 的新数据更新 UI
counterView.setCount(model.getCount())
}
}
}
activity_mvc 布局代码:
xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.app.CounterView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/counter_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
</com.example.app.CounterView>
这样 Activity(Controller) 就很干净了,只负责调度,并不用关心控件的存在。
MVP 拆 Controller:解耦控制器
MVP 拆分的不是 View,它认为 Activity 也是 View(因为它持有 View 的引用)。
MVP 把所有的调度和业务逻辑抽取到了 Presenter 中,通过接口和 Activity 通信。
kotlin
// Model: 业务逻辑和数据
class CounterModel {
private var count = 0
fun increment() {
count++
}
fun getCount(): Int = count
}
// Contract: 定义 View 和 Presenter 的接口
interface CounterContract {
// View 的接口
interface View {
fun showCount(count: Int)
}
// Presenter 的接口
interface Presenter {
fun loadInitialCount()
fun incrementCount()
}
}
// Presenter: 控制器,负责所有逻辑
class CounterPresenter(
private val view: CounterContract.View,
private val model: CounterModel
) : CounterContract.Presenter {
override fun loadInitialCount() {
view.showCount(model.getCount())
}
override fun incrementCount() {
model.increment()
view.showCount(model.getCount())
}
}
// View: Activity,实现接口,所有逻辑委托给 Presenter
class MVPActivity : AppCompatActivity(), CounterContract.View {
private lateinit var presenter: CounterContract.Presenter
private lateinit var countTextView: TextView
private lateinit var incrementButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mvp)
countTextView = findViewById(R.id.count_text_view)
incrementButton = findViewById(R.id.increment_button)
presenter = CounterPresenter(this, CounterModel())
// 绑定按钮点击事件到 Presenter
incrementButton.setOnClickListener {
presenter.incrementCount()
}
// 加载初始数据
presenter.loadInitialCount()
}
override fun showCount(count: Int) {
countTextView.text = count.toString()
}
}
activity_mvp布局文件的代码和view_counter_content布局一样。
Presenter 是一个纯粹的类,而 Activity 变为了被动的视图,只负责实现接口。
现在,我们就知道了 MVC 和 MVP 都是为了解耦 V 和 C,只不过两者的方向不同。MVP 并没有比 MVC 更好。
MVVM 与数据绑定
MVP 解决了耦合性,但也带来了新的问题,随着业务变得复杂,CounterContract.View 接口也会越来越臃肿,Presenter 和 View 存在了大量的命令式调用。
那么我们能不能让 View 订阅数据,我们负责修改数据,View 自动更新呢?
这就是 MVVM 的核心思想:数据绑定。
我们通常会使用 ViewModel 和 LiveData 这两个工具来实现它:
kotlin
// Model: 业务逻辑和数据
class CounterModel {
private var count = 0
fun increment() {
count++
}
fun getCount(): Int = count
}
// ViewModel: 持有 Model,暴露状态
class CounterViewModel : ViewModel() {
// ViewModel 自己创建和管理 Model
private val model = CounterModel()
// 对外暴露 LiveData
private var _count = MutableLiveData<Int>()
val count: LiveData<Int> = _count
init {
// 加载初始状态
_count.value = model.getCount()
}
// 对外暴露业务逻辑方法
fun incrementCount() {
model.increment()
_count.value = model.getCount()
}
}
// View: Activity,持有 ViewModel
// 订阅 ViewModel 的 LiveData,调用 ViewModel 的方法
class MVVMActivity : AppCompatActivity() {
private lateinit var countTextView: TextView
private lateinit var incrementButton: Button
private val viewModel: CounterViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mvvm) // 同 activity_mvp.xml
countTextView = findViewById(R.id.count_text_view)
incrementButton = findViewById(R.id.increment_button)
incrementButton.setOnClickListener {
// 调用 ViewModel 的方法
viewModel.incrementCount()
}
// 订阅 ViewModel 的 LiveData
viewModel.count.observe(this) { newCount ->
// 状态改变时,更新 UI
countTextView.text = newCount.toString()
}
}
}
Activity 不需要实现任何接口,ViewModel 也无需持有 Activity 的引用,真正实现了 V 和 VM 的解耦。
ViewModel 的角色
androidx.lifecycle.ViewModel 最初是为了解决屏幕旋转等配置变更时的数据存活问题而诞生的,无论是 Presenter 或是自定义的 VM 对象,都会在 Activity 销毁时重建。
数据绑定的本质是观察者模式,也可以通过其他方式来实现数据绑定,所以 VM 不一定代表了
androidx.lifecycle.ViewModel。
但由于 ViewModel 的特性,能够持有状态、完成逻辑处理,因此,它完美地承担了 VM 的角色。
所以,现在 MVVM 架构中:
- VM 指的一般是 ViewModel(内部持有
LiveData或StateFlow) - V 指的是 Activity / Fragment / Composable,
- M 是 Model(在复杂项目中通常是
Repository或UseCase)
总结
架构设计的真正核心就是拆分,无论是 MVC(拆分 View)、MVP(拆分 Controller)还是 MVVM(用数据绑定解耦)。它们的目的都是将臃肿的集合体,拆分为清晰的、各司其职的组件。