帶着下面的這個問題開始ViewModel的學習:
ViewModel的生命周期是如何控制的,並且如何保證在一定范圍內的唯一性?
官方文檔里這樣寫到:
The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
ViewModel 簡單來說 這個類是設計用來存儲UI層的數據,以及管理對應的數據,並且這些數據不受配置變化的影響。能夠做到當數據修改的時候,可以馬上刷新Ui效果,比如屏幕的旋轉操作。
引言
Android系統本身提供控件,比如Activity 和Fragment ,這些控件都是具有生命周期方法,這些生命周期方法被系統調用。
-
activity or Fragment 不適於保存大量數據
但是當這些控件因為一些原因被系統隨時銷毀或是重新創建時候,任何存放在這里的數據都有可能會丟失。舉個栗子,Activity中有一個查詢得到的用戶列表,這時候Activity被重建,新的Activity需要再次去獲取用戶數據。如果簡單的數據可以使用控件自帶的方法,將數據保存到onSaveInstances()方法中,在下次OnCreate()中重新將數據取出來,比如UI狀態這類少量數據是可以的,但是對於上述提到的大量的數據,比如列表數據,這樣做就很不合時宜了。
-
在Activity中進行大量的耗時操作和數據的回調管理會造成大量的資源浪費
另一個問題,經常需要在Activty中加載數據,這些數據一般是異步耗時操作,因為獲取數據需要聯網或是花費很長時間。當前的Activity就需要管理這些數據調用,否則可能產生內存泄露的問題。這些回調事件可能會非常耗時,這時候Ui組件管理這些調用的同時,在UI組件銷毀時候還需要清除這些調用。這就造成需要花費更多成本進行維護管理,而且在UI重建時候如configuration change,又需要再次重新調用,造成了很多資源的浪費。
-
Activity的代碼臃腫造成了維護和測試的不友好
同時Ui組件不僅僅只是用來加載數據,更要對用戶的操作作出響應和處理,還要加載其他資源,導致Ui類變的越來越大,越來越臃腫,這就是常說的上帝類。這種情況對代碼的維護和 測試 都是非常不友好的。
前人在這些問題的基礎上開發出了MVP框架 ,創建相同類似於生命周期函數做代理,一方面減少Activity的代碼量,一方面優化了各個功能模塊的邏輯。
ViewModel
Google官方提出的AAC 的ViewModel 就是用於解決上述問題。
ViewModel 用於為Ui組件提供管理數據,並且能夠在需要的時候扔能保持里面的數據。其提供的自動綁定的形式,當數據源有更新的時候可以自動立即的更新Ui效果。
下面看一個官方的小代碼實例:
publicclass MyViewModel extends ViewModel {
privateMutableLiveData<List<User>> users;
publicLiveData<List<User>>getUsers() {
if(users ==null) {
users =newMutableLiveData<List<Users>>();
loadUsers();
}
returnusers;
}
privatevoidloadUsers() {
// do async operation to fetch users
}
}
You can then access the list from an activity as follows:
Activity 訪問User List 數據
publicclass MyActivity extends AppCompatActivity {
publicvoidonCreate(Bundle savedInstanceState) {
MyViewModel model =
ViewModelProviders.of(
this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mViewModelStore.clear()
}
}
當我們獲取ViewModel實例的時候,ViewModel 對象是通過ViewModelProvider保存在LifeCycle中,ViewModel會一直保存在LifeCycle中,直到Activity或是Fragment被銷毀掉,Framework會調用ViewModelStore的clear方法,也就是調用ViewModel的onCleared()方法來進行資源的清理,那么ViewModel 也會被銷毀的。
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
ps:因為ViewModel的生命周期是和Activity分開的,所以在ViewModel中禁止引用任何View對象或者任何引用了Activity的Context的實例對象。如果ViewModel中需要Application的context可以繼承AndroidViewModel類。
那么思考 用戶主動按了返回Home鍵,主動銷毀了這個Activity呢?
這時候系統會調用ViewModel的onClear()方法 清除ViewModel中的數據。
先上一張
ViewModel的生命周期示意圖:

如圖 ,VIewModel相對於Activity 或是Fragment 的生命周期來說非常簡單,就一個生命周期函數:onCleared(),會在Activity的onDestroy()之后執行,那么是不是可以說在Fragment的生命周期函數內也是在onDestroy之后執行呢?
ViewModel的實現過程
//獲取當前類的ViewModel提供者,之后在傳入需要獲得的ViewModel的類型
MyViewModel model = ViewModelProviders.of(this) .get(MyViewModel.class);

解析:如果傳入的是this 是Fragment 就先判斷是否已經關聯到Activity上,沒有就拋出非法參數異常。之后在初始化一個sDefaultFactory對象,用於創建ViewModelProvider,並在viewModelProvider的構造函數中初始化一個ViewModelStores對象

倆個工廠方法用於創建ViewModelStore ,並區分傳入的是Activity 還是 Fragment
以傳入的是Activity為例:

創建FragmentManager對象,並獲取,查找當前的Activity有沒有添加過HoldFragment, 如果沒有呢則去還沒有添加的Activity/Fragment 的 HoldFragment列表中查詢,看看有沒有已經創建的HoldFragment。如果沒有就創建一個新的HoldFragment ,同時給Application注冊一個Activity的生命監聽器,再把創建餓的HoldFragment添加到緩存列表中。
HoldFragment()又是如何操作的呢?

在onDestroy方法中執行了ViewModel的clear方法,當Ui組件被銷毀的時候自動通知Lifecycle進行解除綁定清除ViewModel資源的操作。
簡單總結以上內容:
- 查找當前的Activity/Fragment中是否有已經添加的HoldFragment,有則返回。
- 查找當前的Activity/Fragment是否有已經創建但是並未添加的HoldFragment,有則返回。
- 注冊Activity/Fragment的生命周期監聽。
- 創建新的HoldeFragment,並添加的緩存列表。
- HoldFragment在關聯到Activity/Fragment之后會在緩存中去掉當前的Activity/Fragment對應的HoldFragment
- HoldFragment在onDestory的時候會調用其成員變量mViewStore的clear方法。
回到之前創建ViewModelProvider的地方:

/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in the given {@code store}.
*
* @param store {@code ViewModelStore} where ViewModels will be stored.
* @param factory factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStorestore, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore= store;
}
構造方法中先給兩個成員變量賦值,然后通過ViewModelStore的get方法獲取ViewModel對象
viewModel = mFactory .create(modelClass);
mViewModelStore.put(key,viewModel);
如果獲取不到傳入類的ViewModel 就通過工廠類Factory創建一個新的viewModel 通過put方法添加到ViewModelStore中。
簡而言之就是利用Fragment的方式去獲取生命周期,然后再利用工廠類創建ViewModel。
關於在一定范圍內的唯一性,因為ViewModelStore是HoldFragment的成員變量,HoldFragment是通過FragmentManager添加到指定的Activity/Fragment,那么對於當前的宿主,只有一個HoldFragment,也就只有一個ViewModelStore,同時也就只有一個ViewModel。
ViewModel的在Fragment間的數據分享
有時候一個Activity中的兩個或多個Fragment需要分享數據或者相互通信,這樣就會帶來很多問題,比如數據獲取,相互確定生命周期。
ViewModel可以很好的解決該類問題。有兩個Fragment,一個Fragment提供點擊每個item顯示的詳情,另一個Fragment提供一個列表。那兩個的交互代碼應該是如何表現的呢?
實例代碼如下:
//ViewModel
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;
}
}
//第一個Fragment
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);
});
}
}
//第二個Fragment
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都是通過getActivity()來獲取
ViewModelProvider。這意味着兩個Activity都是獲取的屬於同一個Activity的同一個ShareViewModel實例。
這樣做優點如下:
- Activity不需要寫任何額外的代碼,也不需要關心Fragment之間的通信。
- Fragment不需要處理除SharedViewModel以外其他的代碼。這兩個Fragment不需要知道對方是否存在。
- Fragment的生命周期不會相互影響,即使用其他Fragment替換其中的一個Fragment,另一個依然能也不受影響。
ViewModel和SavedInstanceState對比
最后前文提到保存簡單的數據可以使用Activity自帶的SavedInstanceState方法,那這個和viewMOdel的區別是?
ViewModel使得在屏幕旋轉等操作時候保存數據變得很便捷,但是這不能用於應用被系統kill時的持久化數據。舉個簡單的例子,有一個界面展示國家信息。不應該把整個國家信息放到SavedInstanceState里,而是把國家對應的id放到SavedInstanceState,等到界面恢復時,再通過id去獲取詳細的信息。這些詳細的信息應該被存放在數據庫中。說到數據庫,下篇文章將會介紹Android Architecture Components提供的Room來操作數據庫。
小結
ViewModel其實就是通過給宿主添加Fragment的方式來獲取宿主的生命周期。在HoldFragment中持有一個集合用於保存當前宿主的ViewModel,只需要在onDestroy方法中調用集合的clear方法,就能間接調用到ViewModel的onCleared方法了,這樣實現了對其生命周期的控制。
系列文章列表: