系列文章導航:
- 【譯】Google官方推出的Android架構組件系列文章(一)App架構指南
- 【譯】Google官方推出的Android架構組件系列文章(二)將Architecture Components引入工程
- 【譯】Google官方推出的Android架構組件系列文章(三)處理生命周期
- 【譯】Google官方推出的Android架構組件系列文章(四)LiveData
- 【譯】Google官方推出的Android架構組件系列文章(五)ViewModel
- 【譯】Google官方推出的Android架構組件系列文章(六)Room持久化庫
原文地址:https://developer.android.com/topic/libraries/architecture/viewmodel.html
ViewModel類被設計用來存儲和管理UI相關數(shù)據(jù),以便數(shù)據(jù)能在配置更改(比如屏幕旋轉(zhuǎn))中生存下來。
應用組件,比如activity和fragment,具有由Android框架管理的生命周期。框架可以根據(jù)用戶行為或完全不由你控制的設備事件來決定銷毀還是重建他們。
因為這些對象可能會被操作系統(tǒng)銷毀或重建,任何你持有的這些組件中的數(shù)據(jù)都會丟失。比如,如果在activity中有一個用戶列表,當由于配置更改而引起的activity重建時,新的activity不得不再次拉取用戶列表。對于簡單的數(shù)據(jù),activity可以使用onSaveInstanceState()方法,然后從onCreate()的bundle中恢復數(shù)據(jù),但這種方法僅僅適用于少量數(shù)據(jù),比如UI狀態(tài),對于潛在的大量數(shù)據(jù),比如用戶列表,則不適用。
另外一個問題是,這些UI控制器(activity,fragment等等)經(jīng)常需要做一些需要花費一定時間才能返回的異步調(diào)用。他們需要管理這些調(diào)用,當其銷毀時清理它們,防止內(nèi)存泄漏。這需要大量維護工作,并且當對象由于配置更改重建時,這是浪費資源的,因為它需要發(fā)出相同的調(diào)用。
最后但并非不重要的是,這些UI控制器已經(jīng)需要響應用戶操作或者處理操作系統(tǒng)通信。當他們還需要手動處理其資源時,將使得類膨脹,創(chuàng)造“上帝activity”(或“上帝fragment”),也就是說,采用一個單獨的類來試圖自己處理所有的應用程序工作,而不是將工作委托給其他類。這也使得測試變得更改困難。
將視圖數(shù)據(jù)所有權從UI控制器邏輯分離是更容易和更高效的。Lifecycle提供了一個名為ViewModel的新類,一個負責為UI準備數(shù)據(jù)的輔助類。在配置改變時,ViewModel會自動保留,這樣一來,它持有的數(shù)據(jù)能夠立即對下一個activity或fragment實例可用。在我們上面提到的例子里,獲取以及保存用戶列表數(shù)據(jù)的責任應該是ViewModel的,而不是activity或fragment。
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 async operation to fetch users
}
}
現(xiàn)在activity可以像下面這樣訪問用戶列表:
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創(chuàng)建的同一個MyViewModel實例。當ViewModel的擁有者activity結束時,框架調(diào)用ViewModel的onCleared()方法,以便它可以清理資源。
注意 :因為
ViewModel的生命周期超出了具體的activity和fragment實例,所以它不應該引用任何View或任何可能持有activity context引用的類。
Fragment間共享數(shù)據(jù)
一個activity中的兩個或多個activity需要互相通信的情況是很常見的。這并非微不足道,因為兩個fragment需要定義一些接口描述,并且其所有者activity必須將兩者綁定到一起哦。此外,兩個fragment必須處理其他fragment還沒創(chuàng)建或不可見的情況。
通過使用ViewModel對象可以解決這個常見的痛點。假設一個常見的主-詳細fragment場景:一個包含用戶可以選擇項的列表fragment,另一個fragment用來展示選擇項的內(nèi)容。
這些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 LifecycleFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// update UI
});
}
}
請注意,兩個fragment在獲取ViewModelProvider時都調(diào)用了getActivity()。這意味著,他們兩個將會收到同一個SharedViewModel實例,這個實例的作用域是其activity。
這種方式的好處包括:
- activity不需要做和知道任何關于這次通信的事情。
- fragment不需要互相知道除了
SharedViewModel約束。如果其中一個消失,另一個都能正常工作。 - 每個fragment有自己的生命周期,不受其他fragment生命周期的影響。實際上,
在一個fragment替換另一個fragment的UI中,UI可以正常工作,沒有任何問題。
ViewModel的生命周期
在獲取ViewModel時,ViewModel對象的作用域綁定到傳遞給ViewModelProvider的Lifecycle。ViewModel保留在內(nèi)存中,直到其綁定的Lifecycle永久消失——如果是activity,則是其finish時,如果是fragment,則是其detached時。

ViewModel vs SavedInstanceState
ViewModel提供了一種方便的方法在配置更改之間保留數(shù)據(jù),但是如果應用程序被操作系統(tǒng)殺死,則他們不會保留。
比如,如果用戶離開應用,然后幾個小時后回來,進程在那個時候已經(jīng)被殺掉,Android系統(tǒng)將從保存的狀態(tài)里面還原Activity。所有框架組件(view,acitivty,fragment)使用保存實例狀態(tài)機制來保存他們的狀態(tài),因此大部分時間,你啥也不用做。你可以使用onSaveInstanceState回調(diào)將自定義數(shù)據(jù)加入到bundle中。
通過onSaveInstanceState保存的數(shù)據(jù)是保留在系統(tǒng)進程內(nèi)存中,Android系統(tǒng)允許你僅僅保留一個小塊數(shù)據(jù),因此這不是保留你的應用實際數(shù)據(jù)的好地方。你應該謹慎使用它來存放哪些不容易被UI組件表示的東西。
舉個例子,如果有一個展示國家信息的用戶界面,你永遠也不要把Country對象放到保存實例狀態(tài)中。你可以把countryId放到里面(除非它已經(jīng)由View或Fragment參數(shù)保存)。實際的對象應該保存在數(shù)據(jù)庫中,ViewModel可以通過保存的countryId獲取。