MVVM的簡單介紹

一 概述

MVVM 架構是繼mvc架構後衍生出的一個新的架構, 最先於 2005 年被微軟的 WPF 和 Silverlight 的架構師 John Gossman 提出,而且應用在微軟的軟件開發中,Android開發中常見的模式,有 MVC,MVP,MVVM。Google15年推出databinding 和 2018年推出 LiveData、ViewModel 庫以後,把MVVM正式的應用在 android 上 推動了高潮。本文最後會寫一個官方所支持的demophp

二 MVC、MVP 和 MVVM 區別

2.1 MVC

2.1.1 MVC三個字母的含義

  • View :對應的XML文件
  • Model:實體類javabean
  • Controllor: 對應Activity或者Fragment

2.1.2 模型圖:

備註:摘自任玉剛公衆號的一篇文章java

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 銷燬時銷燬這些數據流。官方說法android

題外話:2.MVVM能夠不用DataBinding,可是android中Google既然已經支持,仍是用DataBinding來處理View的更改,在android中 DataBinding 是實現MVVM的主要組件git

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 元素github

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"
複製代碼

使用方式bash

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

參考資料

相關文章
相關標籤/搜索