Android MVVM探索(三) - ViewModel,DataBinding,LiveData混合三打

之前幾小章我們講了DataBinding,其中將一個普通類化身為ViewModel,但是以我的觀點(diǎn)來看,他僅僅只是一個普通類,一個將各種可觀察屬性封裝起來的普通類,而這個普通類我們還在里面定義了各種相應(yīng)按鈕點(diǎn)擊事件等方法,其實(shí)這些都違背了官方的建議的,我只是想讓大家知道可以這樣做而已。所以我們要介紹Android Jetpack中正統(tǒng)的ViewModel類,以及一些它的最佳實(shí)踐指南。

本篇文章代碼地址

官方中文教學(xué)視頻地址

Android MVVM探索系列

Android MVVM探索(一) - DataBiding初解

Android MVVM探索(二) - DataBiding常用注解

Android MVVM探索(三) - ViewModel,DataBinding,LiveData混合三打

Android Jetpack是谷歌為了幫助開發(fā)者們更快更高效地開發(fā)安卓應(yīng)用而推出來的一套組件。Android Jetpack包含了開發(fā)庫,工具以及最佳實(shí)踐指南。而我們今天要講的ViewModel類是屬于Android Jetpack庫中的lifecycle庫。說到這,順帶解釋以下。lifecycle,中文意思為生命周期。所以這個庫的存在就跟它的中文含義一樣,它可以有效避免內(nèi)存泄漏和解決Android常見的生命周期難題。(如果各位看官不知道內(nèi)存泄露的可以去好好補(bǔ)補(bǔ)課)lifecycle最近發(fā)布了2.0版本,在這個版本中,可以結(jié)合DataBinding進(jìn)行使用,那可以說是方便太多了。

一,ViewModel的定義。

來自官方的解釋:ViewModel類是用來保存UI數(shù)據(jù)的類,它會在配置變更(即 Configuration Change,例如手機(jī)屏幕的旋轉(zhuǎn))之后繼續(xù)存在。

二,它的一些特點(diǎn)。

我們都知道,當(dāng)手機(jī)屏幕發(fā)生旋轉(zhuǎn)的時候,Activity會被重新創(chuàng)建,也就是說生命周期又將從onCreate開始,如果你此時不及時保存,那么一些UI數(shù)據(jù)將會丟失,這樣肯定是會出問題的。但是,ViewModel并不會受此影響,即便手機(jī)屏幕發(fā)生旋轉(zhuǎn),ViewModel依然存在,這樣的話Activity的UI數(shù)據(jù)便可以保存下來。

三,最佳實(shí)踐(官方推薦做法)。

  1. 所有Activity的UI相關(guān)數(shù)據(jù)應(yīng)該保存在ViewModel中,而不是保存在Activity中。這樣做的好處是,在配置變更的時候,你應(yīng)用的UI數(shù)據(jù)仍然存在。即使ViewModel這么強(qiáng)大,但它也不應(yīng)該不承擔(dān)過多責(zé)任,當(dāng)有UI數(shù)據(jù)處理等相關(guān)事件建議創(chuàng)建Presenter類,或者創(chuàng)建一個更成熟的架構(gòu)。

  2. Activity負(fù)責(zé)展示UI數(shù)據(jù),并接收互動(一般來說是與用戶的互動)。但是Activity不應(yīng)當(dāng)處理這些互動。

  3. 在應(yīng)用需要加載數(shù)據(jù)或者保存數(shù)據(jù)的時候,建議創(chuàng)建一個Repository的存儲區(qū)類,里面放置存儲與加載應(yīng)用數(shù)據(jù)的API。

  4. ViewModel不應(yīng)持有Context,就像之前說的:

    它會在配置變更(即 Configuration Change,例如手機(jī)屏幕的旋轉(zhuǎn))之后繼續(xù)存在。

    所以,ViewModel生命周期遠(yuǎn)比Activity,F(xiàn)ragment等生命周期更長,具體如下圖所示。如果你這樣做了,加入在屏幕旋轉(zhuǎn)情況下,原Activity將會銷毀,新的Activity將會被創(chuàng)建。而ViewModel會一直持有原Activity,這樣便會造成內(nèi)存泄漏。如果你的ViewModel確實(shí)需要Context,那么你的ViewModel可以繼承AndroidViewModel,這樣你的ViewModel中會有Application的引用。

    QQ截圖20181029142003.png

  5. ViewModel不應(yīng)當(dāng)取代onSaveInstanceState方法。盡管ViewModel很出色了,但是它和onSaveInstanceState依然是相輔相成的作用。因?yàn)椋?dāng)進(jìn)程被關(guān)閉時,ViewModel將會被銷毀,但是onSaveInstanceState不會受到影響。(個人猜想:比如在后臺內(nèi)存緊張情況下,你的應(yīng)用處于后臺被系統(tǒng)釋放了,ViewModel會被銷毀,但是你通過onSaveInstanceState存儲下來的數(shù)據(jù)在你的應(yīng)用重新回到前臺時仍然可以被恢復(fù))


    QQ截圖20181029143111.png
  6. ViewModel與Activity生命周期對比圖:


    viewmodel-lifecycle.png

四,ViewModel的用法

  1. 引入lifecycle庫:

    implementation "android.arch.lifecycle:extensions:1.1.0"
    annotationProcessor "android.arch.lifecycle:compiler:1.1.0"
    
  2. 首先創(chuàng)建出我們的ViewModel類。我們需要新建一個普通類,(雖然類名是隨意的,但是作為一名合格的程序員,我們?nèi)〉妹恳粋€名字要具有規(guī)范性,要讓代碼閱讀者一看名字就知道這個類或者這個變量是干嘛的)讓它繼承ViewModel類,并且在其中存放UI相關(guān)數(shù)據(jù):

    // 假設(shè)我們要存放的UI數(shù)據(jù)就是User對象
    // 先新建一個實(shí)體對象
    data class User(val name: String, val age: Int, val sex: Int)
    
    // 新建ViewModel類
    // 新建ViewModel類
    class UserViewModel : ViewModel() {
        val user = User("張三", 21, 1)
    }
    
  3. 新建Activity,并且加入實(shí)例化ViewModel的代碼。這里我們實(shí)例化ViewModel不再是new一下:

    class ViewModelActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_view_model)
            val userViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
        }
    }
    

    至此,我們一個ViewModel就創(chuàng)建好了。

  4. 感覺單單一個ViewModel的存在沒有很大的價值,但是如果搭配上LiveData和DataBinding你就能體會到什么是飛一樣的感覺。用上這些,你就能夠創(chuàng)建反應(yīng)式界面(最基本的只要LiveData和ViewModel就可以創(chuàng)建反應(yīng)式界面。或者單單DataBinding就可以完成,但是不具備生命周期感知能力,我們需要手動處理生命周期問題)。也就是說,當(dāng)你底層數(shù)據(jù)發(fā)生變動時(這里暫時只是ViewModel中數(shù)據(jù)發(fā)生變動),UI會自動刷新。

  5. ViewModel只提供一個默認(rèn)的無參構(gòu)造函數(shù),如果你需要一個有參構(gòu)造函數(shù),那么就需要使用ViewModelFactory這個類,具體使用方法如下所示(摘自官方sunflower Demo代碼):

    // 新建一個Factory類,用來提供帶有有參構(gòu)造函數(shù)的ViewModel實(shí)例,必須繼承ViewModelProvider.NewInstanceFactory,然后重寫create方法
    class MessageViewModelFactory(private val message: Message) : ViewModelProvider.NewInstanceFactory() {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return MessageViewModel(message) as T
        }
    }
    
    // 新建一個帶有有參構(gòu)造函數(shù)的ViewModel
    class MessageViewModel(val message: Message) : ViewModel()
    
    // Activity中初始化ViewModel的代碼也要進(jìn)行改動
    // 創(chuàng)建Factory對象
    val factory = MessageViewModelFactory(Message("我是通過有參構(gòu)造函數(shù)直接初始化的信息內(nèi)容",
            "我是通過有參構(gòu)造函數(shù)直接構(gòu)造出來的信息發(fā)送人"))
    // 通過Factory對象初始化帶參構(gòu)造函數(shù)的ViewModel
    val messageViewModel = ViewModelProviders.of(this, factory).get(MessageViewModel::class.java)
    // 將MessageViewModel實(shí)例賦值給xml中的msg
    binding.msg = messageViewModel
    

    這樣便可以完成帶有有參構(gòu)造函數(shù)的ViewModel的初始化。

五,搭配上LiveData

  1. LiveData簡介:LiveData是一種具有生命周期感知能力的可觀察數(shù)據(jù)持有類。它同屬于Android Jectpack中的lifecycle庫。LiveData對象通常保存在我們上面講的ViewModel中。

  2. 結(jié)合ViewModel的使用。我們說過,使用LiveData加ViewModel可以創(chuàng)建一個反應(yīng)式界面,那么我們應(yīng)該怎么做呢?我們需要改寫上面的UserViewModel,讓其中的user具有可觀察性:

    class UserViewModel : ViewModel() {
        val user = MutableLiveData<User>()
    }
    
  3. 接著,我們在ViewModelActivity中監(jiān)聽UserViewModel中的user屬性的變化,并且在它變化后進(jìn)行相應(yīng)的操作:

    <!-- 為了演示效果,我們在布局文件activity_view_model中添加了一些控件 -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".viewmodel.ViewModelActivity">
    
        <TextView
            android:id="@+id/tv_vm"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <Button
            android:id="@+id/bt_vm"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="設(shè)置姓名"/>
    </LinearLayout>
    
    // 在Activity中設(shè)置數(shù)據(jù)變化監(jiān)聽,并且進(jìn)行相應(yīng)處理
    class ViewModelActivity : AppCompatActivity() {
        private lateinit var tv: TextView
        private lateinit var bt: Button
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_view_model)
            tv = findViewById(R.id.tv_vm)
            bt = findViewById(R.id.bt_vm)
            val userViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
            // 監(jiān)聽ViewModel中user的變化,當(dāng)它變化時,將TextView重新設(shè)置文字
            userViewModel.user.observe(this, Observer {
                tv.text = it?.name
            })
            // 為按鈕設(shè)置點(diǎn)擊事件,點(diǎn)擊后設(shè)置user的值
            bt.setOnClickListener{
                val user = User("張三", 21, 1)
                userViewModel.user.value = user
                // Java代碼
                // userViewModel.user.setValue(user)
            }
        }
    }
    

    寫完以上代碼,當(dāng)我們點(diǎn)擊按鈕的時候TextView就會顯示“張三”二字了。

  4. 上面代碼中除了setValue,還有一個方法叫postValue。區(qū)別在于setValue只可以在主線程執(zhí)行(即UI線程),postValue只可以在后臺線程運(yùn)行。

  5. LiveData能夠感知生命周期的好處:1,當(dāng)Activity不在屏幕上時(不可見),LiveData不會出發(fā)沒必要的界面更新;2,當(dāng)Activity被銷毀時,LiveData將自動清空與Observer的連接;

六,搭配上DataBinding

我們看到,上面的代碼還是有些繁瑣,我們還要自己寫代碼監(jiān)聽數(shù)據(jù)變化,并且自己手動去更新UI。之前我們DataBinding可不是這樣的。是的,我們還可以更簡單。別忘了,lifecycle2.0是支持了DataBinding數(shù)據(jù)綁定的。我們可以通過以下步驟來結(jié)合DataBinding使用:

  1. 像DataBinding中那樣寫布局,所以將activity_view_model改成如下所示:

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
        <data>
            <variable
                name="viewModel"
                type="top.cyixlq.test.viewmodel.UserViewModel"/>
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            tools:context=".viewmodel.ViewModelActivity">
    
            <TextView
                android:id="@+id/tv_vm"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{viewModel.user.name}"/>
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{String.valueOf(viewModel.user.age)}"/>
    
            <Button
                android:id="@+id/bt_vm"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="設(shè)置姓名"/>
    
        </LinearLayout>
    </layout>
    
  2. 注釋掉ViewModelActivity中的之前的代碼,并重新寫,所以有用的代碼就是下面這樣:

    class ViewModelActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_view_model)
            val userViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
            val binding = DataBindingUtil.setContentView<ActivityViewModelBinding>(this, R.layout.activity_view_model)
            // Java代碼
            // ActivityViewModelBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_view_model)
    
            binding.viewModel = userViewModel
            // java代碼
            // binding.setViewModel(userViewModel)
    
            // 讓xml內(nèi)綁定的LiveData和Observer建立連接,也正是因?yàn)檫@段代碼,讓LiveData能感知Activity的生命周期
            binding.setLifecycleOwner(this)
        }
    }
    
  3. 為了驗(yàn)證效果,我們設(shè)置一下按鈕的的點(diǎn)擊事件,當(dāng)按鈕點(diǎn)擊后TextView顯示姓名年齡的信息:

    bt_vm.setOnClickListener { 
        userViewModel.user.value = User("李四", 22, 1)
    }
    // java代碼
    // findViewById(R.id.bt_vm).setOnClickListener(new View.OnClickListener() {
    //     userViewModel.user.setValue(new User("李四", 22, 1))
    // })
    

經(jīng)過以上的步驟就可以結(jié)合DataBinding使用了

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

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