MVVM的简单介绍

5,658 阅读6分钟

一 概述

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的角度讲)

  1. 优点
  • Xml就是View层,与java逻辑代码解耦,具有一定的解耦
  1. 缺点
  • 没有固定的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 优缺点

  • 优点:
  1. Model和View之间解耦,两者交互由Presenter完成,把部分的逻辑的代码从Fragment和Activity业务的逻辑移出来
  2. Activity或者Fragment只是用来展示控件,或者控制控件的显示和隐藏,以及View的变化
  • 缺点:
  1. 随着业务的增加,IView的接口和IPresenter的接口会越来越多
  2. 更换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 优缺点

  • 优点 :
  1. View和数据双向绑定,当model变化时会自动同步给View
  2. crash会降低,xml里面自己会有判断
  3. View(Activity或者Fragment)层就是View层,不处理任何关于数据的逻辑
  • 缺点 :
  1. Xml里面写代码,对于android有点不习惯,并且会使xml臃肿
  2. 无法快速定位crash位置,Debug比较困难
  3. 双向绑定不利于View的复用,因为每个xml里面都有一个model,但是每个页面的mode有可能不一样
  4. 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 引入其他的依赖

  1. ViewModel and LiveData ,不包含在ViewModel和LiveData中使用协程
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
  1. 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) //发射给出去
    }
  1. 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

        }
    }
}
  1. 在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 ()
  1. 在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 }
            }
    }
}

具体的demo地址github

参考资料