MVVM 四大件

MVVM 四大件

一. DataBinding

1.1 引入

在對應Model的build.gradle中加入:java

android {
    dataBinding {
        enabled = true
    }
}
複製代碼

在佈局時:android

<?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>

 </data>

 <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">

     <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" 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>
複製代碼

原有的佈局被一個layout所包裹起來了,其中的<data>標籤內,咱們能夠聲明須要使用到的變量及其類型。面試

咱們聲明一個實體類:markdown

public class User{
 private String userName;
 private String userCode;
 private String userGender;
 private String userTel;

 public User(String userName, String userCode, String userGender, String userTel) {
     this.userName = userName;
     this.userCode = userCode;
     this.userGender = userGender;
     this.userTel = userTel;
 }

 public String getUserName() {
     return userName;
 }

 public void setUserName(String userName) {
     this.userName = userName;
 }

 public String getUserCode() {
     return userCode;
 }

 public void setUserCode(String userCode) {
     this.userCode = userCode;
 }

 public String getUserGender() {
     return userGender;
 }

 public void setUserGender(String userGender) {
     this.userGender = userGender;
 }

 public String getUserTel() {
     return userTel;
 }

 public void setUserTel(String userTel) {
     this.userTel = userTel;
 }
}
複製代碼

而後咱們在頁面的邏輯中,須要展現這個類的一些屬性,咱們在Databinding中,首先須要創建:視圖和Model,即視圖和數據之間的聯繫,讓視圖可以主動地感知到數據的 變更。架構

因此,咱們在layout.xml中的data標籤加入:app

<variable name="displayUserEntity" type="com.example.mvvm1_databinding.User" />
複製代碼

name能夠本身填寫,type則指定爲:User實體類。mvvm

咱們也可使用<import>標籤,將整個User包引入到<data>標籤中,以便多個<variable>使用:ide

<data>

 <import type="com.example.mvvm1_databinding.User" />
 <variable name="displayUserEntity" type="User" />

 <variable name="createNewUserEntity" type="User" />
</data>
複製代碼

若是咱們導入了不一樣包下的User,能夠用alias屬性,指定別名。工具

<data>

     <import type="com.example.mvvm1_databinding.User" />

     <import alias="UserOfOthers" type="com.example.mvvm1_databinding.others.User" />

     <variable name="displayUserEntity" type="User" />

     <variable name="createNewUserEntity" type="UserOfOthers" />
 </data>

複製代碼

接下來,回到只有一個displayUserEntity和一個<variable>的狀況,咱們在真正的Layout中,建立幾個Textview,用於數據的顯示:佈局

<?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>

     <variable name="displayUserEntity" type="com.example.mvvm1_databinding.User" />
 </data>

 <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" android:orientation="vertical" tools:context=".MainActivity">

     <TextView android:id="@+id/tv_user_code" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{displayUserEntity.userCode}" />

     <TextView android:id="@+id/tv_user_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{displayUserEntity.userName}" />

     <TextView android:id="@+id/tv_user_gender" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{displayUserEntity.userGender}" />

     <TextView android:id="@+id/tv_user_tel" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{displayUserEntity.userTel}" />
 </LinearLayout>
</layout>
複製代碼

咱們能夠爲其設置一個默認值,用於佔位:android:text="{displayUserEntity.userName,default="預覽佔位"}"。:

在真正的佈局文件中,咱們能夠獲取到data中聲明的屬性相關的值。

Activity中,咱們須要構建相關的實體類,模擬數據的填充:

class MainActivity : AppCompatActivity() {

 private lateinit var user: User

 override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     setContentView(R.layout.activity_main)
     //怎樣去操做displayUserEntity?

     initData()
     doDataBinding()
 }

 private fun initData(){
     user = User("Jack", "17854124", "男", "400000000")
 }

 private fun doDataBinding() {
     val activityBinding: ActivityMainBinding =
         DataBindingUtil.setContentView(this, R.layout.activity_main)

     activityBinding.displayUserEntity = user
 }
}
複製代碼

這樣一來,咱們代碼中的數據可以被顯示在界面上了,而且沒有通過繁瑣的findViewById(R.id.x).setText(value)

至此,咱們就完成了數據從Model流向View層的流程。可是此時,咱們新增一個按鈕,聲明事件:點擊按鈕改變user類的數值,並不能作到視圖中的數據也被修改,即作不到雙向數據綁定(Model即便被修改了,也沒法作到View層同步修改)。此時解決的問題,僅僅是省略了繁瑣的setText。

所以,如何作到雙向數據綁定呢?

1.2 Observable

咱們指望的是,當Model層的數據被修改時,視圖層可以主動感知到數據的變化,從而本身去更新視圖。這和觀察者模式自己相契合。

1. BaseObservable

BaseObservable提供了兩個方法:notifyChange()和notifyPropertyChanged(),前者會刷新全部的值域,後者則只更新對應註釋的屬性,咱們能夠在實體類中使用@Bindable標記一個但願被觀察的對象。(若是是私有屬性則標記在get方法上)

咱們讓Entity實體類繼承自BaseObservable類。而後再某個屬性上標記@Bindable:

咱們讓Entity實體類繼承自BaseObservable類。而後再某個屬性上標記@Bindable:

public class User extends BaseObservable {
 private String userName;
 private String userCode;
 private String userGender;
 private String userTel;

 public User(String userName, String userCode, String userGender, String userTel) {
     this.userName = userName;
     this.userCode = userCode;
     this.userGender = userGender;
     this.userTel = userTel;
 }

 @Bindable
 public String getUserName() {
     return userName;
 }

 public void setUserName(String userName) {
     this.userName = userName;
     notifyChange();//只要userName變化了,就刷新全部屬性
 }

 @Bindable
 public String getUserCode() {
     return userCode;
 }

 public void setUserCode(String userCode) {
     this.userCode = userCode;
     notifyPropertyChanged(BR.userCode);//僅僅刷新界面上的userCode屬性。
 }

 public String getUserGender() {
     return userGender;
 }

 public void setUserGender(String userGender) {
     this.userGender = userGender;
 }

 public String getUserTel() {
     return userTel;
 }

 public void setUserTel(String userTel) {
     this.userTel = userTel;
 }
}

// 注意,kotlin請在build.gradle中的plugins塊內加入kapt的配置:
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
}
// 不然@Bindable會報錯

複製代碼

這樣一來,咱們就對userName屬性進行了監聽,當useName屬性發生改變時,視圖就能主動感知了。

如今,咱們添加幾個方法,用於事件的更新,一樣地使用Databinding,咱們在原先的data標籤處:

<variable name="eventBinding" type="com.example.mvvm1_databinding.MainActivity.EventBinding" />
複製代碼

新增一個eventBinding屬性,該屬性的type指向了MainActivity下的一個內部類:

inner class EventBinding{
        fun tryLoadNewData(view:View){
            this@MainActivity.user.apply {
                userName = "Chris"
                userCode = "154751512"
                userGender = "女"
                userTel = "500000000"
            }
        }

        fun updateUserCode(view:View){
            //修改UserCode並不會觸發所有數據的更新,由於是notifyPropertyChange
            this@MainActivity.user.userTel = "5555550"
            this@MainActivity.user.userCode = "1"
        }

        fun updateUserName(view:View){
            //修改Name會觸發全部的修改,notifyChange
            this@MainActivity.user.userGender = "不男不女"
            this@MainActivity.user.userName = "abc"
        }

        fun resetData(view:View){
            this@MainActivity.initData()
        }
    }
複製代碼

在佈局文件中,新增幾個Button,用於觸發事件:

<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="切換用戶" android:onClick="@{eventBinding::tryLoadNewData}" tools:ignore="HardcodedText" />

        <Button android:id="@+id/btn_update_user_code" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="更新用戶Code,只更新了本身" android:onClick="@{eventBinding::updateUserCode}" >

        </Button>

        <Button android:id="@+id/btn_update_user_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="更新用戶Name,且會觸發全部屬性在界面上的更新" android:onClick="@{eventBinding::updateUserName}" >

        </Button>
複製代碼

最後,咱們在doDataBinding方法中,設置dataBinding的eventBinding屬性便可:

private fun doDataBinding() {
        activityBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        activityBinding.displayUserEntity = user
        eventBinding = EventBinding()

        activityBinding.eventBinding = eventBinding
    }
複製代碼

這樣一來,咱們就能夠看清楚notifyChangednotifyPropertyChanged的區別了。

咱們也能夠設置一個監聽器:

user.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
            override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
                Log.d("MVVM",propertyId.toString())
            }
        })
複製代碼

//當有被@Bindable修飾的屬性被修改時,此方法會被回調。打印的是被修飾變量的Id,而不是所有被更新的屬性Id各打印一次。例如更新userTel userName userCode時,只有後面兩個屬性的值會被打印出來。

2. ObservableField

對一些基本數據類型的封裝,也能夠填入泛型參數。

3. ObservableCollections

用於替代原生的 ListMap,分別是 ObservableListObservableMap,當其包含的數據發生變化時,綁定的視圖也會隨之進行刷新。在視圖中,採用list[index]或者是map[key]來訪問。

二. LiveData和ViewModel

在上文的DataBinding工具的介紹中,咱們知道,咱們能夠很輕易地創建:View與視圖Layout的鏈接,咱們的後臺數據利用Observe能夠輕鬆地構建響應式的佈局。實際上,這已經解決了MVC、MVP中的一個很是之繁瑣的一個操做:findViewById、setText,咱們只須要在Bean中的數據更新後,調用notify相關的方法便可更新到視圖。這一切都基於DataBinding。Databinding自己更像是Activity和XML文件的黏合劑,使得View層的功能更加明確,沒必要再大量書寫setText等修改視圖的代碼。

可是,這個步驟能不能再簡單一點?回顧一下咱們在面試過程當中複習了無數遍的MVP和MVVM的區別,MVVM分三層,M、V、VM,M和V都好說,Presenter被替換成了VM,VM即ViewModel。

原先的Presenter任務繁重的緣由很大程度上是由於Presenter做爲"主持人",須要持有V、M的引用,View層會利用Presenter去調用Model層的代碼去取數據,而Model層又會使用Presenter的方法返回數據到視圖。一個簡單的功能,在Presenter中卻要兩個方法共同處理。

在引入VM後,得益於觀察者模式,界面能夠直接監聽Model數據的變化,這樣一來,VM層相對於原先的P層,進化了一點:只須要單向地處理View指派給Model層的任務了,這樣減小了大量的代碼。官方ViewModel類的出現很大程度上是爲了規範對MVVM架構的實現。另外,它的一個派生類:AndroidViewModel能夠得到運行時的Application。

咱們直接繼承自ViewModel便可使用:

//別忘了添加依賴
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

public class MyViewModel extends ViewModel {
    private final MutableLiveData<User> mUser = new MutableLiveData<>(new User(
            "Jack",
            "170000",
            "男",
            "12939487210"
    ));

    private final MutableLiveData<Integer> videoId = new MutableLiveData<>(-1);
    private final MutableLiveData<String> videoTitle = new MutableLiveData<>("Example");


    public MutableLiveData<User> getmUser() {
        return mUser;
    }
}


複製代碼
//MainActivity中
private fun initViewModel() {    
    mMyViewModel = ViewModelProvider(this).get(MyViewModel::class.java);    
    //觀察這個LiveData<User>,一旦產生屬性變化,那麼就回調到此處。 
    mMyViewModel.getmUser().observe(this, Observer {        
        Log.d("MVVM",it.toString())    
    })
}
複製代碼

咱們將user對象從Activity中搬運到了ViewModel中,這樣一來,存儲變量的任務就交給了ViewModel,而不是Activity。

三. Lifecycle

即生命週期,若是某個組件具備生命週期,那麼能夠實現接口:LifecycleOwner,即說明本身是一個生命週期的持有者,重寫

@NonNull
Lifecycle getLifecycle();
複製代碼

得到生命週期。

而lifecycle自己纔是真正的生命週期,其中枚舉了State的全部狀態:

public enum State {
    DESTROYED,
    INITIALIZED,
    CREATED,
    STARTED,
    RESUMED;
}
複製代碼

以及全部可能發生的相關ON事件:

public enum Event {
    ON_CREATE,
    ON_START,
    ON_RESUME,
    ON_PAUSE,
    ON_STOP,
    ON_DESTROY,
    ON_ANY
}
複製代碼

咱們能夠自定義類繼承自:LifecycleObserver來監聽生命週期:

package com.example.lifecycle;

import android.util.Log;

import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;

import java.util.concurrent.Semaphore;

public class MyUtil implements LifecycleObserver {
    private String TAG = "MY_UTIL";
    private int count = 0;
    private Thread runner;

    private Semaphore semaphore = new Semaphore(1);//定義信號量

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    private void onCreate() throws InterruptedException {
        Log.d(TAG,"ON_CREATE");
        runner = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(500);
                    semaphore.acquire();//上鎖
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(TAG, String.valueOf(count++));
                semaphore.release();//釋放鎖
            }
        });
        semaphore.acquire();
    }


    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    private void onResume(){
        Log.d(TAG,"ON_RESUME");
        if(!runner.isAlive()){
            runner.start();
        }
        semaphore.release();//釋放掉鎖
    }


    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    private void onPause() throws InterruptedException {
        Log.d(TAG,"ON_PAUSE");
        semaphore.acquire();//得到鎖
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    private void onDestroy(){
        Log.d(TAG,"ON_DESTROY");
        runner.interrupt();
    }
}

//MainActivity.java的onCreate中
getLifecycle().addObserver(new MyUtil());

複製代碼

這是一個很簡單的例子,在onResume時,開啓一個線程計數並輸出,退出到桌面時,回調到onPause時,暫停計數輸出,若是銷燬則終止線程的執行。代碼自己很簡單,可是有一個要特別注意的點是,若是咱們將addObserver的操做放在onStart()中,咱們仍然可以收到ON_CREATE和ON_START事件(效果很像粘性事件)。

package com.example.lifecycle;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onResume() {
        super.onResume();
        getLifecycle().addObserver(new MyUtil());//
    }
}

複製代碼

四. 總結

在MVP中,View層持有P層引用,同時:

  1. 須要實現V經過P調用M的方法(請求數據
  2. 須要實現M經過P調用V的方法(更新視圖
  3. 存儲Activity自身的局部變量
  4. 簡單用戶交互管理、UI聲明與管理(不涉及IO)
  5. 管理生命週期,合理調度,恢復數據。

在引入DataBinding + ViewModel + LiveData後:

  1. 須要實現V經過P調用M的方法(請求數據)- 只須要監聽數據,數據基本處理交給VM層,VM層去Call Model層的方法。
  2. 須要實現M經過P調用V的方法(更新視圖)- DataBinding + LiveData解決
  3. 存儲Activity自身的局部變量 - ViewModel解決
  4. 響應用戶輸入事件、UI聲明與管理(不涉及IO)- 這一點不變
  5. 管理生命週期,合理調度 - 至於恢復數據等操做,實際上ViewModel中存儲LiveData可以很好地幫助咱們處理數據,即便橫屏重建也不會致使數據丟失。

因此,原先須要集中寫在MainActivity.java中的五個功能點,如今只剩下1.5個:

  1. 簡單用戶交互管理(不涉及IO,響應OnClick事件或者簡單地視圖改變等等)
  2. 管理生命週期,合理調度。

進一步引入lifecycle之後,管理生命週期也能夠剝離出來,這樣一來,View層的職責就完徹底全變成了:

  1. 響應用戶輸入事件、UI聲明與監聽LiveData(不涉及IO)

這是很是理想的一個View層模型。這樣咱們View層相關的類有:

//layout.xml
class MainActivity extends AppcompatActivity(){}
//1.響應用戶輸入 ->對應4

public class MyViewModel extends ViewModel{}
//1.存儲變量 ->對應3
//2.請求Model層的方法,並在回調中修改LiveData的值,界面值因爲DataBinding能夠獲得同步修改。 -> 對應一、2

public class MyLifecycleObserver implements LifecycleObserver{}
//1.監聽生命週期 -> 對應5

複製代碼

這樣一來,相比較MVP層的P層,View層的職責更加清晰,P層的基於接口的代碼量也大大降低(不須要再處理數據從M流向V層的接口方法了)。

相關文章
相關標籤/搜索