帶着下面的這個問題開始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中有一個查詢獲得的用戶列表,這時候Activity被重建,新的Activity須要再次去獲取用戶數據。若是簡單的數據可使用控件自帶的方法,將數據保存到onSaveInstances()方法中,在下次OnCreate()中從新將數據取出來,好比UI狀態這類少許數據是能夠的,可是對於上述提到的大量的數據,好比列表數據,這樣作就很不合時宜了。
另外一個問題,常常須要在Activty中加載數據,這些數據通常是異步耗時操做,由於獲取數據須要聯網或是花費很長時間。當前的Activity就須要管理這些數據調用,不然可能產生內存泄露的問題。這些回調事件可能會很是耗時,這時候Ui組件管理這些調用的同時,在UI組件銷燬時候還須要清除這些調用。這就形成須要花費更多成本進行維護管理,並且在UI重建時候如configuration change,又須要再次從新調用,形成了不少資源的浪費。
同時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()又是如何操做的呢?
在onCreate方法中執行了將在未添加到Activity或是Fragment的HolderFragment列表中刪除當前的Activity 或是Fragment。
在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方法了,這樣實現了對其生命週期的控制。