- 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的任務分離出來是必要的,這也就是上面所說的
分離關注點
原則
下面則是示意圖

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
加到7,當裝置旋轉
資料仍能得到保留,即使更改系統語言都可以,也就是說如果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。
以上便是ViewModel在整個Jetpack的作用和功能了,現在隻是冰山一角,慢慢學習。