天天看點

Android Jetpack -- ViewModel篇

  • Android Jetpack架構元件
    • Android Jetpack – ViewModel篇
    • Android Jetpack – LiveDate篇
    • Android Jetpack – DataBinding篇
    • Android Jetpack – Navigation篇
    • Android Jetpack – Lifecycles篇

ViewModel是一個負責為Fragment/Activity配置和管理資料的類,同時處理Fragment/Activity與Application其餘部分的通信。

ViewModel始終與Scope(Fragment/Activity)相關聯建立,同時一直保留當Scope存在時,例如Activity被finish。換句話說,ViewModel不會随着其擁有者因配置更改被銷毀而銷毀(旋轉,橫豎屏切換),新的擁有者會重建立立與ViewModel的關聯。

ViewModel旨在擷取和儲存Activity或Fragment必要的資訊,Activity或Fragment能夠觀察到ViewModel的資料改變,ViewModel通常通過LiveData或Android Data Binding暴露資料,也可以在你喜歡的架構中使用可觀察構造。

ViewModel隻負責管理UI資料,不應通路視圖結構和持有Activity或Fragment的引用。

ViewModel的出現主要為了解決兩個問題:

1.當Actvitiy銷毀重建過程中的資料恢複問題,雖然原來可以使用onSaveInstanceState()來完成,但是隻支援能被序列化的資料而且是小量資料,對于大量資料則顯得有點無力。

2.UI控制器的工作繁忙,UI控制器主要用于處理顯示,互動,其他的額外操作可以委托給其他類完成,将不應該配置設定給UI的任務分離出來是必要的,這也就是上面所說的

分離關注點

原則

下面則是示意圖

Android Jetpack -- ViewModel篇

ViewModel執行個體

ViewModel在配置更改期間能自動保留其對象,以便它們所持有的資料可立即用于下一個

Activity

或片段

Fragment

下面通過一個具體的執行個體:

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private MyViewModel myViewModel;

    private TextView tv;
    private Button button1, button2;

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

        myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);

        tv = findViewById(R.id.textView);
        tv.setText(String.valueOf(myViewModel.num));
        button1 = findViewById(R.id.button1);
        button2 = findViewById(R.id.button2);

        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.num++;
                tv.setText(String.valueOf(myViewModel.num));
            }
        });

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.num += 2;
                tv.setText(String.valueOf(myViewModel.num));
            }
        });
    }
}
           

MyViewModel.java

public class MyViewModel extends ViewModel {

    public int num = 0;

}
           

布局如下:Welcome對應TextView,+1對象button1,+2對應button2

Android Jetpack -- ViewModel篇

加到7,當裝置旋轉

Android Jetpack -- ViewModel篇
Android Jetpack -- ViewModel篇

資料仍能得到保留,即使更改系統語言都可以,也就是說如果Activity被重建,将接收到第一個Activity建立的相同的MyViewModel執行個體。當所有者Activity完成後,會調用ViewModel對象的onCleared()方法,以便它能夠清理資源。

同時這裡也需要注意一個問題,當ViewModel持有外部引用的時候會阻止回收,是以

ViewModel絕不能引用View、Lifecycle(也是Jetpack元件之一)或任何可能包含對Activity上下文的引用的類。

回到最上面的那個圖,圖說明了

ViewModel

的作用域涉及到整個生命周期,當擷取ViewModel時,ViewModel的生命周期限定為傳入ViewModelProvider的對象的生命周期。也就是對于以下場景(引用官方示例)

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.
        });
    }
}
           

由于傳入的是

Activity

,是以其作用域為整個

Activity

,不同的

Fragment

可以通過

ViewModelProviders

擷取到同一個

ViewModel

,這樣有以下的好處:

  • Activity無須參與Fragment之間的互動。Activity與Fragment無關
  • Fragment之間也無需互相建立聯系,Fragment與Fragment無關
  • 每個Fragment都有自己的生命周期,即使被替換也不會有任何影響。

加強ViewModel,支援異常生命周期

我們知道,有些時候在Activity被意外殺死,如清理背景等會直接跳過onDestory()而是回調onSaveInstanceState()異常殺死下的生命周期,這個時候ViewModel也會被殺死,再次恢複的時候便會被重建,這樣,原來的資料也就丢失了,是以我們需要改進一下ViewModel以支援異常退出情況下的重建。

首先很容易是想到通過

onSaveInstanceState()

來儲存,然後通過

SaveInstanceState

來恢複,雖然也是一種可行的方法,但是好像全程跟ViewModel沒什麼關聯,未免優點沒有物盡其用,是以ViewModel也提供了類似SavedInstanceState的方法。

SavedStateHandle :用于儲存狀态的資料類型,是一個key-value的map,其實也就是類似Bundle。采用Hashmap來實作的。
           

具體使用:

public class ViewModelWithData extends ViewModel {

    private MutableLiveData<Integer> number;
    private SavedStateHandle handle;

    private static final String KEY_NUMBER = "number";

    public ViewModelWithData(SavedStateHandle handle) {
        this.handle = handle;
        number = new MutableLiveData<>();
        number.setValue(0);
    }

    public MutableLiveData<Integer> getNumber() {//每次都通過SavedStateHandle來擷取相應的值
        if (!handle.contains(KEY_NUMBER)) {
            handle.set(KEY_NUMBER, 0);
        }
        return handle.getLiveData(KEY_NUMBER);
    }

    public void addNumber(int n) {
        getNumber().setValue(getNumber().getValue() + n);
    }
}
           
public class LiveDataActivity extends AppCompatActivity {

    private ViewModelWithData viewModelWithData;

    ActivityLiveDataBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_live_data);
		//需要将建立的SavedStateVMFactory傳入
        viewModelWithData = ViewModelProviders.of(this, new SavedStateVMFactory(this)).get(ViewModelWithData.class);

        binding.setData(viewModelWithData);
        binding.setLifecycleOwner(this);
    }

}
           

好了,這樣就完成了将VIewModel的生命周期與整個Activity的同步,包括異常情況下。

支援SharedPreference等使用到Application的相關

因為

SharedPreference

需要使用到

Application

來擷取到,是以要想配合ViewModel還需要傳入Application作為參數,當然,Jetpack已經為我們準備好了

AndroidViewModel:感覺應用上下文的ViewModel,繼承自ViewModel。

其實實作也很簡單,傳入Application就行了,需要時擷取。

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}
           

然後剩下的便可以通過擷取SharedPreference來存儲資料,再配合LiveData,SaveStataHandler實作跨越整個App的生命周期同步。當然,資料的持久化存儲不僅僅時這一種方式,配合任意方式都可。

使用ViewModel取代Loader

特别對于資料庫這類資料,由于需要保持UI與資料庫間的資料同步,常常導緻在UI控制器(Activity等)進行資料加載,這有違

分離關注點

的原則。

使用

VIewModel

配合

Room

LiveData

能夠友善的取代

CusorLoader

的操作,

VIewModel

保證資料的穩定性,

Room

通知資料更改,

LiveData

則自動使用更新的資料更新UI。

Android Jetpack -- ViewModel篇

以上便是ViewModel在整個Jetpack的作用和功能了,現在隻是冰山一角,慢慢學習。