Android 中的 MVP 与 MVVM
立泉初学 Android 只知道Activity可以控制 UI,并不懂设计模式,也不知道操作数据的逻辑代码应该如何归类,索性全堆砌在Activity和Fragment里。但随着功能逻辑的复杂化,Activity结构开始变得混乱、臃肿、难以维护,在长文件里到处跳跃的感觉岂止酸爽。后来看到 MVP 顿时豁然开朗,原来可以这样,它将View和Model分离用Presenter连接,代码中 UI 驱动的逻辑立刻清晰整洁。
我在工作中大量使用 MVP,之后接触 Jetpack,其DataBinding、LiveData和ViewModel向我预示一个新的设计模式: MVVM。它的View和Model概念与 MVP 一致,不同的是ViewModel,把视图和数据双向绑定,当数据发生变化时视图自动更新,而视图变化也会直接作用到数据上。这种比 MVP 更简洁的结构让我很感兴趣。
古老的 MVC
Model View Controller

View即视图,用于接收 UI 事件,控制 UI 状态。Model即数据模型,用于处理数据,比如操作数据库和网络中。Controller即控制器,接收View传来的事件,定义具体业务逻辑,调用Model操作数据再由它通知View更新 UI。在 Android 中通常是Activity或Fragment。
通过一个简单例子理解 MVC:点击Button查询天气,在TextView中显示结果并弹出Toast。
Model
/**
* 执行数据查询并报告结果
*/
class WeatherModel(private val callBack: Callback) {
fun queryWeather() {
// 从网络中查询天气
val weather = ...
// 查询完成后,报告结果
callBack.querySuccess(weather)
}
}
/**
* 结果回调接口
*/
interface Callback {
fun querySuccess(weather: String)
}
View
/**
* 接收 UI 点击事件,控制 UI
*/
class WeatherView(private var activity: Activity, onBtnClickListener: OnBtnClickListener) : Callback {
private val btn = activity.findViewById<Button>(R.id.btn)
private val tv = activity.findViewById<TextView>(R.id.tv)
init {
// 收到点击事件
btn.setOnClickListener {
// 通知 Controller 执行操作
onBtnClickListener.onClick()
}
}
/**
* UI 显示天气
*/
fun showWeather(weather: String) {
tv.text = weather
Toast.makeText(activity, weather, Toast.LENGTH_SHORT).show()
}
/**
* Model 处理完成后调用 View 更新 UI
*/
override fun querySuccess(weather: String) {
showWeather(weather)
}
}
/**
* 用于通知 Controller 点击事件的监听器
*/
interface OnBtnClickListener {
fun onClick()
}
Controller
/**
* 接收 UI 事件,控制 Model 处理
*/
class WeatherActivity : Activity(), OnBtnClickListener{
private lateinit var model: WeatherModel
private lateinit var view: WeatherView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_weather)
view = WeatherView(this, this)
model = WeatherModel(view)
}
/**
* View 被点击时调用 Model 处理数据
*/
override fun onClick() {
model.queryWeather()
}
}
UI 层被从Activity中剥离,Activity作为Controller持有View和Model。View反向持有Controller以实现在点击事件发生时触发查询,Model需要更新 UI 则持有View的回调接口,Controller调用Model查询数据,完成后由它回调View更新 UI。这是一个闭环结构,组件循环依赖并不符合低耦合规则。
进化的 MVP
Model View Presenter

View即视图,用于接收 UI 事件,控制 UI 状态,一般是Activity或Fragment。Model即数据模型,用于处理数据,比如操作数据库和网络。Presenter即逻辑实现类,从View中接收事件,向下调用Model处理数据、获取结果,再向上控制View更新 UI。
同样的例子,用 MVP 是这样:
Model
/**
* 执行数据查询并返回结果
*/
class WeatherModel(private val callBack: Callback) {
fun queryWeather(): String {
// 从网络中查询天气
val weather = ...
// 查询完成后,返回结果
return weather
}
}
View
/**
* 定义操作 UI 的接口
*/
interface IWeatherActivity {
fun showWeather(weather: String)
}
/**
* Activity 实现操作 UI 的接口
*/
class WeatherActivity : Activity(), IWeatherActivity {
private lateinit var iWeatherPresenter: IWeatherPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_weather)
// 创建 Presenter
iWeatherPresenter = WeatherPresenter(this)
// Kotlin 可直接用 id 访问 View 实例
btn.setOnClickListener {
// 点击事件发生时,调用 Presenter 执行操作
iWeatherPresenter.queryWeather()
}
}
/**
* UI 显示天气
*/
override fun showWeather(weather: String) {
tv.text = weather
Toast.makeText(activity, weather, Toast.LENGTH_SHORT).show()
}
}
Presenter
/**
* 定义响应 UI 事件的接口
*/
interface IWeatherPresenter {
fun queryWeather()
}
/**
* 实现接口,构造器要求传入 View 以操作 UI
*/
class WeatherPresenter(private val iWeatherActivity: IWeatherActivity) : IWeatherPresenter {
/**
* 具体的执行点击事件
*/
override fun queryWeather() {
val weatherModel = WeatherModel()
// 调用 Model 获取数据,然后调用 View 更新 UI
iWeatherActivity.showWeather(weatherModel.queryWeather())
}
}
在 MVP 模式中View和Model完全分离,Activity作为View持有Presenter,Presenter则持有View和Model。当点击事件发生时,View通知Presenter执行操作,Presenter调用Model获取数据,然后调用View更新 UI。由于IPresenter接口的存在,实际上可根据需求创建多个不同实现的Presenter实例,具有很高的灵活性。
双向绑定的 MVVM
Model View ViewModel

View即视图,用于接收 UI 事件,控制 UI 状态,一般是Activity或Fragment。Model即数据模型,用于处理数据,比如操作数据库和网络。ViewModel即视图模型,从View中接收事件,向下调用Model获取数据,数据被修改后View将随之自动更新。
MVP 已经足够好,既能实现View和Model的分离又能使用多Presenter实例改变 UI 事件的行为。但MVC和MVP都有一个共同特点,即 UI 由数据驱动,数据变化后必须使用Model或Presenter主动更新 UI。而MVVM可实现数据和 UI 的绑定,当数据变化时 UI 自动更新,这在 Android 上的实现基础是 Jetpack 组件里的DataBinding和LiveData。
DataBinding把数据和定义 UI 的 XML 文件绑定,LiveData支持数据感知其所在组件的生命周期,有效生命周期内的数据变化会自动触发 UI 改变。
同样上面的例子,用 MVVM 是这样:
Model
/**
* 执行数据查询并返回结果
*/
class WeatherModel() {
fun queryWeather(): String {
// 从网络中查询天气
val weather = ...
// 查询完成后,返回结果
return weather
}
}
View
首先在Layout资源文件中定义DataBinding:
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义 DataBinding 中要和 View 绑定的数据 -->
<data>
<variable
name="weather"
type="String"/>
</data>
<!-- 正常的 View 布局 -->
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="40dp"
<!-- 内容设置为 DataBinding 的 weather 变量,
点击按钮时,这里会随数据变化自动更新 -->
android:text="@{weather}"/>
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Show Weather"/>
</LinearLayout>
</layout>
用Activity作为View:
// 注意是 AppCompatActivity
class WeatherActivity : AppCompatActivity() {
private lateinit var dataBinding: ActivityWeatherBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 DataBinding 将视图资源加载到 Activity上
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_weather)
// 创建 ViewModel,LiveData 保存在 ViewModel 中
val weatherViewModel = ViewModelProviders.of(this).get(WeatherViewModel::class.java)
// 绑定视图和数据
dataBinding.weather = weatherViewModel.weatherLiveData.value
// 数据发生变化时这里会得到通知
weatherViewModel.weatherLiveData.observe(this, Observer<String> {
// 弹出天气信息,注意这里的回调方法运行在 LiveData 发生变化的线程里
tv.text = weatherViewModel.weatherLiveData.value
Toast.makeText(this, weatherViewModel.weatherLiveData.value, Toast.LENGTH_SHORT).show()
})
// DataBinding 可直接使用 id 获取 View 实例
dataBinding.btn.setOnClickListener {
// 调用 ViewModel 执行点击操作
weatherViewModel.queryWeather()
}
}
}
ViewModel
/**
* 执行数据查询并报告结果
*/
class WeatherViewModel : ViewModel() {
private val weatherModel = WeatherModel()
// LiveData 保存天气数据
var weatherLiveData: MutableLiveData<String> = MutableLiveData()
init {
// 初始化数据
weatherLiveData.value = ""
}
fun queryWeather() {
// 从 Model 中查询天气
val weather = weatherModel.queryWeather()
// 查询完成后更新 LiveData 数据,监听此 LiveData 的观察者会得到通知
weatherLiveData.value = weather
}
}
可见 MVVM 与 MVP 最大的不同,当ViewModel处理事件、更新数据后 UI 是自动刷新的,而非由Presenter调用View更新视图的方法。此外可使用DataBinding直接在视图 XML 文件里定义 UI 对数据的响应操作,实现数据变化后 UI 自主调整显示方式。MVVM 把数据和 UI 绑定只需在ViewModel中更新数据而不用关心 UI 如何显示它们。
结语
我在工作中大量使用 MVP,对 MVC 和 MVVM 只是耳闻,接触 Kotlin 和 Jetpack 后尝试在练习中使用这些新东西,编程不再是入门时枯燥的堆砌代码而是像打造艺术品一样津津有味,这样的变化真实而有趣。