Android Architecture Components(3) - ViewModel

上一篇文章中咱們介紹了Architecture Components中的LifeCycle,LifeCycleOwner及LifeCycleObserver,不知道你們掌握的怎麼樣?在學習編碼的路上,仍是要多多實踐才能夠呢。 接下來咱們要介紹的是ViewModel。android


ViewModel簡介數據庫

ViewModel是用來存儲和管理生命週期過程敏感的界面數據的一個類,用ViewModel存儲的數據能夠在應用設置項發生改變時保存下來例如當屏幕旋轉。緩存

Android框架層管理UI控件的生命週期好比Activity和Fragment。框架層須要決定在面對用戶交互時什麼時候銷燬或者重建UI控件,這一過程不是由開發者控制的。bash

若是系統銷燬活着重建UI控件,那麼用戶的輸入數據活着你已緩存的UI數據都會丟失,例如,在你的應用中有一個Activity展現着一個用戶列表,當屏幕發生旋轉時,Activity被重建,那麼新建立的Activity須要再去請求一次用戶列表數據。對於一些簡單的數據,咱們可使用onSavedInstanceState()方法存儲在Bundle中,而後在onCreate()函數中恢復,可是這種狀況只適用於少許而且能夠被序列化的數據,並不適用於其餘數據,例如說一個用戶列表或者不少圖片。網絡

另外一個問題是UI控件須要頻繁的發起異步請求並等待返回結果。UI控件須要去管理這些異步請求並保證在它完成後被系統清理掉以免內存泄漏。這種管理須要大量的耐心和細心,而且在這些對象由於設置改變而重建的情形下,形成了一種資源浪費。app

Activity和Fragment這種UI空間只是去展現UI數據,響應用戶交互或者處理系統交互,例如說請求權限,UI控件同時也須要負責從數據庫或者網絡上加載數據,咱們應該進行責任分攤,不要爲UI控件添加過多的操做,這樣會致使一個類去處理一個應用所要處理的工做,讓咱們的測試工做變得更加艱難。框架

綜合以上幾點,Google推出了ViewModel,用於幫助UI控件準備數據,ViewModel會在設置發生變化時自動存儲數據,他們所持有的數據能夠馬上被新建的Fragment或者Activity複用。異步

##ViewModel的使用 假設咱們須要在一個 應用頁面內展現一個用戶列表,那麼咱們能夠將數據請求操做託管給ViewModel,代碼以下:async

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }
    private void loadUsers() {
        // Do an asyncronous operation to fetch users.
    }
}
複製代碼

實現一個ViewModel只須要繼承自ViewModel便可,隨後咱們能夠在Activity中訪問數據,方式以下:ide

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}
複製代碼

若是這個Activity被重建,那麼他會接收到上一個Activity所建立的MyViewModel對象,當持有該ViewModel對象的Activity銷燬時,系統會調用ViewModel.onCleared()方法釋放資源。

注意ViewModel對象毫不能持有View,LifeCycle或者任何持有Activity 引用的對象

在設計理念上,ViewModel的生命週期獨立於View或者LifeCycleOwner,這種設計也意味着你能夠更簡單的爲ViewModel編寫測試用例以覆蓋ViewModel中的操做。ViewModel能夠持有LifeCycleObservers好比說LiveData,若是ViewModel須要使用Application的引用,例如說去獲取一個系統服務,此時能夠繼承自AndroidViewModel,該類有一個構造函數,能夠接受Application的引用。

這裏我再舉一個簡單的ViewModel的例子,以便你們更好的理解ViewModel。這個例子是界面上有一個Button和一個TextView,TextView用於記錄Button 的點擊次數,在屏幕旋轉時保持TextView上的次數不變。

首先編寫佈局文件,代碼以下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.code.archicomponentssmaples.MainActivity">
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:text="Click Me"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
複製代碼

這裏我試用了約束佈局,不懂得同窗能夠自行百度,或者使用LinearLayout/RelativeLayout本身實現便可。

編寫ViewModel類,用於持有Button的點擊次數,以下:

public class ClickCounterViewModal extends ViewModel {
    private int count = 0;
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}
複製代碼

隨後在Activity中使用該ViewModel緩存Button點擊次數,代碼以下:

public class LifeOwnerActivity extends AppCompatActivity {

  private TextView mTextView;
  private Button mButton;
  private ClickCounterViewModal mClickCounterViewModal;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_life_owner);
    mClickCounterViewModal = ViewModelProviders.of(this).get(ClickCounterViewModal.class);
    initView();
    initData();
  }

  private void initView(){
    mTextView = (TextView)findViewById(R.id.owner_textView);
    mButton = findViewById(R.id.owner_Button);
  }

  private void initData(){
    displayClickCount();

  mButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      mClickCounterViewModal.setCount(mClickCounterViewModal.getCount()+1);
        displayClickCount();
      }
    });
  }

  private void displayClickCount(){
    mTextView.setText(mClickCounterViewModal.getCount()+"");
  }
}
複製代碼

如上述代碼ViewModel對象須要使用ViewModelProviders.of(Context)進行初始化。

另外多說一點ViewModel的提出只是爲了處理UI相關數據的緩存問題,並不表明它能徹底替代onSavedInstanceState()的做用。

在這個例子中ViewModel的工做流程以下圖:

這裏寫圖片描述

##ViewModel生命週期

ViewModel對象的生命週期依賴於初始化時ViewModelProvider傳入的LifeCycle對象,ViewModel對象會常駐在內存中直到與其對應的LifeCycle被銷燬,對於Activity而言,就是當其被finish時,對於Fragment而言就是當它被detach的時候。 下圖說明了一個Activity在發生屏幕旋轉時自身的生命週期變化以及與其對應的ViewModel的生命週期。

這裏寫圖片描述

一般狀況下,在System調起Activity時,咱們在Activity的onCreate()函數內初始化ViewModel對象,隨後系統可能屢次調用該Activity的onCreate()函數,例如說發生屢次屏幕旋轉。這種清醒下ViewModel來源於第一次onCreate(),直到調用Activity.finish()時,該ViewModel對象纔會被銷燬並釋放資源。

##使用ViewModel在Fragment間共享數據

在咱們平常變成生活中,Activity須要與其內部的一個或多個Fragment交互信息的需求比比皆是,假設又這樣一種情形,咱們有一個Activity頁面,左右各一個Fragment,左側展現列表,右側展現列表中某一項的詳情。這種情形下Fragment中須要定義接口,持有Fragment的Activity須要同時綁定這兩個Fragment,而且一個Fragment要處理另外一個Fragment沒有建立或顯示的問題。

這種痛點能夠經過ViewModel解決,這兩個Fragment能夠公用一個ViewModel對象,在Activity內處理,示例代碼以下:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
    public void select(Item item) {
        selected.setValue(item);
    }
    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}
複製代碼

如上所示,兩個Fragment只須要監聽ViewModel中值的變化更新UI便可。 須要注意的是這兩個Fragment在ViewModelProvider內傳入了getActivity(), 此時該ViewModel對象的生命週期徹底依賴於持有兩個Fragment的Activity。

這種模式有以下幾點好處:

  • Activity不須要關注交互中的任何事;
  • Fragments彼此不須要知道對方的狀態,一個Fragment消失了,另外一個仍然能夠正常工做;
  • 每一個Fragment有其獨立的生命週期,不會彼此影響,若是一個Fragment被另外一個Fragment替換了,UI仍然能正常顯示;

##使用ViewModel代替Loaders

相似於CursorLoader的加載類常常被頻繁的用於異步維護界面和數據庫的數據一致,如今你也能夠用ViewModel和一些其餘的輔助類來實現這種功能了,使用ViewModel可使咱們的界面控制與數據加載解耦。 一種常見的使用CursorLoader監聽數據庫變化的結構圖以下,當一個數據庫值發生改變時,加載器會自動觸發一個從新加載事件,完成後更新UI。

這裏寫圖片描述
當ViewModel與Room以及LiveData結合使用時,就能夠徹底替代加載器的功能,ViewModel保證數據在設置發生改變的過程當中不清空,當數據發生改變時,Room通知LiveData,隨後使用新的數據更新UI。
這裏寫圖片描述
當數據變得愈來愈複雜時,你或許會使用一個單獨的類去加載數據,ViewModel的目的就在於保證數據在生命週期變化過程當中不被清空,關於LiveData,Room相關的更多詳細內容,咱們會在下一篇推文中詳細介紹,感謝你們閱讀。更多詳細內容歡迎你們關注個人公衆號 [圖片上傳失敗...(image-feea9f-1512483891026)]
相關文章
相關標籤/搜索