一 概述
MVVM 架构是继mvc架构后衍生出的一个新的架构, 最早于 2005 年被微软的 WPF 和 Silverlight 的架构师 John Gossman 提出,并且应用在微软的软件开发中,Android开发中常见的模式,有 MVC,MVP,MVVM。Google15年推出databinding 和 2018年推出 LiveData、ViewModel 库之后,把MVVM正式的应用在 android 上 推进了高潮。本文最后会写一个官方所支持的demo
二 MVC、MVP 和 MVVM 区别
2.1 MVC
2.1.1 MVC三个字母的含义
- View :对应的XML文件
- Model:实体类javabean
- Controllor: 对应Activity或者Fragment
2.1.2 模型图:
备注:摘自任玉刚公众号的一篇文章
2.1.3 优缺点(以android的角度讲)
- 优点
- Xml就是View层,与java逻辑代码解耦,具有一定的解耦
- 缺点
- 没有固定的model层,Activity中代表Controllor,网络接口的model也在Activity中,一个逻辑稍微复杂的页面代码行数都上千行,影响阅读,Activity既然控制着View,又含有Model。相当耦合
2.2 MVP
2.2.1 MVP三个字母的含义
- View 对应于Activity和Xml,负责页面的展示
- Model 实体类javabean
- Presenter 控制器 负责完成View于Model间的交互与逻辑处理(网络和逻辑)
2.2.2 模型图
2.2.3 优缺点
- 优点:
- Model和View之间解耦,两者交互由Presenter完成,把部分的逻辑的代码从Fragment和Activity业务的逻辑移出来
- Activity或者Fragment只是用来展示控件,或者控制控件的显示和隐藏,以及View的变化
- 缺点:
- 随着业务的增加,IView的接口和IPresenter的接口会越来越多
- 更换Xml里面的控件会引起IView接口和IPressenter接口的改动
2.3 MVVM(只从android考虑)
2.3.1 MVVM三个字母的含义
- View 还是Activity或者fragment,也就是Xml,负责页面的展示,或者控制控件的显示和隐藏,以及View的变化,不参与任何逻辑和数据的处理
- Viewmodel 主要负责业务逻辑和数据处理,本身不持有 View 层引用,通过 LiveData(如果项目中有Rxjava可以不引用LiveData) 向 View 层发送数据,通过DataBinding更改View中的UI层
- Model:实体类javabean,便是指这里的Repository ,主要负责从本地数据库或者远程服务器来获取数据,Repository统一了数据的入口,获取到数据,将数据发送 :摘自本文章
题外话:1.项目中不一定用LiveData,Rxjava本身就是观察者模式,如果项目中引用到Rxjava,可以抛弃LiveData,但是请确保正确处理应用的生命周期。特别是,确保在相关的 LifecycleOwner 停止时暂停数据流,并在相关的 LifecycleOwner 销毁时销毁这些数据流。官方说法
题外话:2.MVVM可以不用DataBinding,但是android中Google既然已经支持,还是用DataBinding来处理View的更改,在android中 DataBinding 是实现MVVM的主要组件
2.3.2 模型图
2.3.3 优缺点
- 优点 :
- View和数据双向绑定,当model变化时会自动同步给View
- crash会降低,xml里面自己会有判断
- View(Activity或者Fragment)层就是View层,不处理任何关于数据的逻辑
- 缺点 :
- Xml里面写代码,对于android有点不习惯,并且会使xml臃肿
- 无法快速定位crash位置,Debug比较困难
- 双向绑定不利于View的复用,因为每个xml里面都有一个model,但是每个页面的mode有可能不一样
- xml里面写java代码,不能用kotlin的简单语言
三 编写一个MVVM的demo例子
具体的demo地址github
3.1 引入
3.1.1 DataBinding引入
在应用模块的 build.gradle 文件中添加 dataBinding 元素
android {
...
dataBinding {
enabled = true
}
}
之后你在xml的根布局中 ,mac按住command+return ,window 按住 art+enter 就会出现这样的标记,选第一个就会出现DataBinding的layout。如果没有这个选项,重启studio就可以了
3.1.2 引入其他的依赖
- ViewModel and LiveData ,不包含在ViewModel和LiveData中使用协程
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
- LiveData 使用协程
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
使用方式
val user: LiveData<User> = liveData {
val data = getNetData() // loadUser is a suspend function.
emit(data) //发射给出去
}
- ViewModel 中使用协程
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
会用方式
class MainViewModel(private val repository: MainUserRepository) : ViewModel() {
var userData = MutableLiveData<User>()
var progressShow = MutableLiveData<Boolean>()
fun click(view: View) {
getUser()
}
private fun getUser() {
//在这里开协程
viewModelScope.launch {
progressShow.value = true
userData.value = repository.getUser()
progressShow.value = false
}
}
}
- 在activity中使用懒加载模式得到ViewModel,只能用val修饰
implementation "androidx.activity:activity-ktx:1.1.0"
使用方式:直接在acitiviy中调用下面的方法,另外这个还需要在app的build.gradle中加入一个配置。当然了,我们也可以用别的懒加载方式加载ViewModel,就可以不用引用这个依赖了,比如
//不用添加此依赖的方式获取懒加载ViewModel
val viewModel:MyViewModel by lazy { ViewModelProvider(this).get(MyViewModel::class.java) }
//使用此依赖需要配置
android {
kotlinOptions {
jvmTarget = "1.8"
}
...
}
// 这里只能用val来修饰
private val viewModel: MyViewModel by viewModels ()
- 在Fragment中使用懒加载模式得到ViewModel,这个跟activity中的懒加载一个道理
implementation "androidx.fragment:fragment-ktx:1.2.4"
使用方式
private val viewModel : MyViewModel by viewModels()
3.1.3 编写
数据类
data class User(var name:String,var age:Int)
UesrActivity
class UserActivity : AppCompatActivity() {
//创建ViewModel方式1
// private val viewModel: UserViewModel by viewModels()
//创建ViewModel方式2
// private val viewModel:UserViewModel by lazy { ViewModelProvider(this).get(UserViewModel::class.java) }
private val viewModel: UserViewModel by viewModels {
// 通过InjectorUtils类拿到对应的ViewModelFactory,这种方法可以定义构造方法带参的ViewModel
InjectorUtils.provideUserViewModelFactory()
}
private var progressDialog: ProgressDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var binding =
DataBindingUtil.setContentView<ActivityUserBinding>(this, R.layout.activity_user)
//绑定生命周期,声明这个 Activity 为 Data Binding 的 lifecycleOwner
binding.lifecycleOwner = this
//设置xml中的viewModel
binding.viewmodel = viewModel
// viewModel的progressShow 来判断是否是加载中的状态
viewModel.progressShow.observe(this, Observer<Boolean> {
if (it) showLoading()
else stopLoading()
})
}
/**
* 显示加载框
*/
private fun showLoading() {
if (progressDialog == null) {
progressDialog = ProgressDialog(this)
progressDialog!!.setMessage("加载中")
}
progressDialog!!.show()
}
/**
* 关闭加载框
*/
private fun stopLoading() {
progressDialog?.dismiss()
}
companion object {
fun startMe(activity: Activity) {
activity.startActivity(Intent(activity, UserActivity::class.java))
}
}
}
activity_user.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<!-- 引入User的model-->
<variable
name="viewmodel"
type="com.nzy.mvvmsimple.user.UserViewModel" />
<!-- View ,可以使用 View.GONE 和 View.VISIBLE -->
<import type="android.view.View" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewmodel.userData.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bv_get_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:onClick="@{viewmodel::click}"
android:text="获取网络数据"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_name" />
<ProgressBar
android:layout_width="50dp"
android:layout_height="50dp"
android:visibility="@{viewmodel.progressShow ? View.VISIBLE : View.GONE}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
InjectorUtils类:用来获取各种各样的ViewModelFatory
object InjectorUtils {
fun provideUserViewModelFactory(): UserViewModelFactory {
val repository = getUserRepository()
return UserViewModelFactory(repository)
}
private fun getUserRepository(): UserRepository {
return UserRepository.getInstance()
}
}
UserViewModelFactory
class UserViewModelFactory(
private val repository: UserRepository
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
// 传入一个 Repository 参数
return UserViewModel(repository) as T
}
}
UserRepository:获取数据的网络或者数据库
/**
* 获取用户信息的Repository
*/
class UserRepository private constructor() {
/**
* 获取User的接口
*/
suspend fun getUser(): User? {
delay(3000)
return User("王者荣耀之马大黑", 18)
}
companion object {
// For Singleton instantiation
@Volatile
private var instance: UserRepository? = null
fun getInstance() =
instance ?: synchronized(this) {
instance ?: UserRepository().also { instance = it }
}
}
}