一篇文章徹底弄懂Android-MVVM

在學(xué)習(xí)一個(gè)技術(shù)之前,我們首先要搞清為什么要用它、用它以后會(huì)有什么好處,這樣我們才能有興趣的學(xué)習(xí)下去。

一、為什么要用MVVM?

我為什么要用這個(gè)什么MVVM,我就平常寫(xiě)和它有什么不同嗎?

首先我們要說(shuō)一下,使用MVVM后,程序會(huì)有哪些變化:

1.MVVM并不會(huì)提升程序的性能,甚至如果用不好還會(huì)降低性能。

2.MVVM會(huì)增大代碼的總量。

3.閱讀MVVM的代碼你必須不停的跳來(lái)跳去,跳到你惡心想吐。

啥?你在搞笑嗎?那我為什么還要用它?且慢,我還沒(méi)說(shuō)最重要的一條。

4.MVVM將讓你的程序完全解耦。當(dāng)然,是正確使用的前提下。

為了最后這一條,我們用前3條的犧牲完全不過(guò)分,真正寫(xiě)代碼的時(shí)候,我們面對(duì)的往往不是性能的些許提升,也不是幾十或幾百K的包大小增大帶來(lái)的問(wèn)題,而是程序不停迭代所帶來(lái)的程序穩(wěn)定性要求。而這也是MVVM大顯身手的地方,MVVM能夠完全讓你的業(yè)務(wù)功能組件化,讓我們需要什么就調(diào)什么,并且組件可以在不同頁(yè)面之間復(fù)用;在寫(xiě)業(yè)務(wù)組件的時(shí)候,我們可以完全集中精力,只管寫(xiě)組件要完成的功能,而無(wú)需分神它顧。

讀到這,相信你應(yīng)該對(duì)MVVM稍微有點(diǎn)興趣了吧。

二、MVVM到底是什么?

MVVM,是Model、View、ViewModel三者的縮寫(xiě),它是一種程序的設(shè)計(jì)框架,是一種設(shè)計(jì)思路。不同的人實(shí)現(xiàn)MVVM,所用的構(gòu)成技術(shù)、實(shí)現(xiàn)以后的性能都是不一樣的。千萬(wàn)不要覺(jué)得它有多深?yuàn)W,他其實(shí)就是由一系列代碼(或技術(shù))構(gòu)成的一個(gè)程序的底座。我們?cè)诹己玫鬃祥_(kāi)發(fā)出來(lái)的程序穩(wěn)定性更高,可擴(kuò)展性更強(qiáng)。

下面我們挨個(gè)來(lái)來(lái)說(shuō)Model、View、ViewModel。

1.Model:數(shù)據(jù)提供。

Model在程序中專門用于提供數(shù)據(jù),不管是網(wǎng)絡(luò)請(qǐng)求獲得的數(shù)據(jù),還是數(shù)據(jù)庫(kù)獲得的數(shù)據(jù),統(tǒng)統(tǒng)寫(xiě)在Model里。Model層獨(dú)立性相當(dāng)強(qiáng),它只用來(lái)提供數(shù)據(jù),而不管數(shù)據(jù)是用來(lái)做什么的。

2.View:視圖元素和視圖元素初始化。

View在Android中指代的就是我們常見(jiàn)的布局文件和Activity中的元素初始化部分??傊?,所有一切我們?cè)贏ndroid上肉眼能看見(jiàn)的東西都是View。在View層里,我只對(duì)UI做初始化,比如將TextView設(shè)置字體大小,為Banner控件設(shè)置滾動(dòng)速度等等,這些大多可以直接在布局文件中完成。

3.ViewModel:操作業(yè)務(wù)數(shù)據(jù),并將數(shù)據(jù)呈現(xiàn)在View上。

ViewModel根據(jù)業(yè)務(wù)需要,從Model層調(diào)取相關(guān)數(shù)據(jù),然后更新View層相關(guān)元素。

說(shuō)起來(lái)有點(diǎn)抽象,別急,下面我們用一個(gè)簡(jiǎn)單的例子來(lái)解釋它們?cè)鯓踊ハ嗯浜?,你很快就?huì)明白。

在例子中,我們使用Google提供的DataBinding技術(shù)來(lái)完成數(shù)據(jù)綁定,以實(shí)現(xiàn)View和ViewModel層的交互。

【??時(shí)間】

例子

例子非常非常簡(jiǎn)單,一個(gè)倉(cāng)庫(kù)管理的頁(yè)面,一共三個(gè)元素,其中更新時(shí)間和倉(cāng)庫(kù)物品列表是從服務(wù)器動(dòng)態(tài)獲取的。

平常寫(xiě)的話,我們會(huì)在Activity內(nèi)完成所有的操作,先進(jìn)行網(wǎng)絡(luò)請(qǐng)求,得到數(shù)據(jù)后為TextView設(shè)置文字、為RecycleView設(shè)置Adapter。但使用了MVVM后,代碼將被拆分為三個(gè)部分,我們按View、ViewModel、Model的順序直接上代碼,隨后在一一解釋:


View層:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="stockViewModel"
            type="com.ben.shop.viewmodel.StockManagementViewModel" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView

            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white"
            android:gravity="center"
            android:padding="@dimen/dp_5"
            android:text="倉(cāng)庫(kù)管理系統(tǒng)"
            android:textStyle="bold" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/dp_10"
            android:gravity="center"
            android:text="@{stockViewModel.bindingData.component2()}"
            android:textColor="@color/red"
            android:textSize="@dimen/sp_15" />

        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:stockGoodsViewLoader="@{stockViewModel.bindingData.component1()}" />

    </LinearLayout>

</layout>

ViewModel層:

class StockManagementViewModel(mvvmViewModel: MVVMViewModel?) : BaseViewModel(mvvmViewModel) {

    fun load() {
        val newData = getModel(StockModel::class.java).getStockList()
        val time = getModel(StockModel::class.java).getUpdateTime()
        bindingData.data.clear()
        bindingData.data.addAll(newData)
        bindingData.updateTime = "更新時(shí)間:${time}"
        bindingData.notifyChange()
    }

    override fun onResumed() {
        super.onResumed()
        load()
    }

    val bindingData: BindingData = BindingData(mutableListOf(), "")

    data class BindingData(
            @Bindable val data: MutableList<StockGood>,
            @Bindable var updateTime: String
    ) : BaseObservable()

}

@BindingAdapter("android:stockGoodsViewLoader")
fun stockGoodsViewLoader(recyclerView: RecyclerView, data: List<StockGood>) {
    recyclerView.apply {
        layoutManager = LinearLayoutManager(recyclerView.context).apply {
            orientation = LinearLayoutManager.VERTICAL
        }
        adapter = StockManagementAdapter(recyclerView.context, data)
    }

}

Model層:

class StockModel(mvvmModel: MVVMModel?) : HttpModel(mvvmModel) {
    fun getStockList(): List<StockGood> {
        return mutableListOf<StockGood>().apply {
            add(StockGood("1", "鉗子", 3))
            add(StockGood("2", "扳手", 12))
        }

    }

    fun getUpdateTime(): String {
        return Utils.DateUtil.getCurrentlyTimeByFormatter(Constant.DateFormat4)
    }

}

View層在我們這個(gè)例子中只有一個(gè)布局文件,布局文件是DataBinding的標(biāo)準(zhǔn)寫(xiě)法,在<data></data>標(biāo)簽中寫(xiě)好控件需要的ViewModel。

<data>
        <variable
            name="stockViewModel"
            type="com.ben.shop.viewmodel.StockManagementViewModel" />
    </data>

然后將TextView和RecyclerView與ViewModel中的元素綁定好。

android:text="@{stockViewModel.bindingData.component2()}"
android:stockGoodsViewLoader="@{stockViewModel.bindingData.component1()}"

View沒(méi)看明白沒(méi)關(guān)系,先來(lái)看ViewModel。

還記得ViewModel的作用嗎,我來(lái)重復(fù)一遍:它從Model層獲取數(shù)據(jù),然后更新視圖。

這里ViewModel實(shí)現(xiàn)了Activity的生命周期函數(shù),onResumed是ViewModel開(kāi)始執(zhí)行的地方

調(diào)用Model獲取數(shù)據(jù):

        val newData = getModel(StockModel::class.java).getStockList()
        val time = getModel(StockModel::class.java).getUpdateTime() 

更新視圖

        bindingData.data.clear()
        bindingData.data.addAll(newData)
        bindingData.updateTime = "更新時(shí)間:${time}"
        bindingData.notifyChange()

這里注意, notifyChange方法是BaseObservable中的方法,用于通知視圖刷新

因?yàn)槲覀円呀?jīng)在View層將數(shù)據(jù)綁定到了data和updateTime兩個(gè)變量上,因此,我們只需要更新這兩個(gè)變量,就可以完成View層視圖的刷新,這也正是MVVM強(qiáng)大的地方。從這里就可以看出,View層和ViewModel層是高度解耦的,只通過(guò)兩個(gè)變量相互關(guān)聯(lián),這樣,ViewModel就有了非常高的獨(dú)立性,可以輕而易舉的被其他View調(diào)用,也可以從例子中的View層瞬間剔除,就像插上或拔出一個(gè)U盤那樣簡(jiǎn)單。

Model層比較容易理解,由于只是一個(gè)例子,我們?cè)谶@里并沒(méi)有請(qǐng)求網(wǎng)絡(luò),實(shí)際開(kāi)發(fā)過(guò)程中,你只需要使用你想使用的網(wǎng)絡(luò)工具請(qǐng)求網(wǎng)絡(luò)或調(diào)用數(shù)據(jù)庫(kù),獲取到數(shù)據(jù)后返回給ViewModel層即可。

這里值得一題的是Activity中的代碼:


class StockManagementActivity : MVVMActivity<ActibityStockManagementBinding>() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentViewByDataBinding(R.layout.actibity_stock_management)
        dataBinding.stockViewModel = getViewModel(StockManagementViewModel::class.java)
    }

}

可以看到,Activity中幾乎沒(méi)有邏輯代碼,僅僅有一行來(lái)實(shí)例化ViewModel。MVVM就這樣,將業(yè)務(wù)邏輯完整從Activity中拆分為獨(dú)立的三塊,不僅解放了Activity,更使整個(gè)代碼的邏輯清晰,可讀性極強(qiáng),并且功能塊之間互不干擾,這也就實(shí)現(xiàn)了我們開(kāi)頭所說(shuō)的組件化開(kāi)發(fā)。

三、總結(jié)

怎么樣,MVVM還不錯(cuò)吧?如果您曾經(jīng)了解過(guò)MVC、MVP,相信本文一定很容易理解,MVVM只不過(guò)的它們的“升級(jí)版”,實(shí)現(xiàn)過(guò)程不同,目標(biāo)卻是相同的。這些設(shè)計(jì)思想并不是十分深?yuàn)W的東西,本質(zhì)就是讓軟件質(zhì)量變得更好,如果不明白它們的原理,不僅不會(huì)讓代碼質(zhì)量提高,反而會(huì)使程序變得不知所云,得不償失。

另外說(shuō)一下,本文主講MVVM,DataBinding的用法和原理沒(méi)有過(guò)多涉及,但相信看過(guò)本文以后,您應(yīng)該對(duì)它也有了一定的了解了吧。我開(kāi)頭說(shuō)過(guò),MVVM實(shí)現(xiàn)的技術(shù)各不相同。DataBinding是Goodle出品,并且Android Studio中支持的相當(dāng)不錯(cuò),因此被我采用。在了解了MVVM的機(jī)制以后,你完全可以選擇你想使用的技術(shù),構(gòu)建你自己的MVVM框架。如果各位有興趣,我可以寫(xiě)一個(gè)關(guān)于DataBinding的文章。當(dāng)然網(wǎng)絡(luò)上關(guān)于DataBinding的也很多,大家也可以作為參考。

小生才疏學(xué)淺,文中必有遺漏和錯(cuò)誤,十分歡迎閱讀過(guò)的同學(xué)指正,大家一起討論!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容