官方文檔鏈接:https://developer.android.google.cn/topic/libraries/architecture/viewmodel.html
1.前言
LiveData雖然解決了數(shù)據(jù)變化的及時反饋,但由于是在組件中創(chuàng)建LiveData對象的,所以當組件生命周期結(jié)束,此對象便會釋放,這將導致哪些問題呢?下面來講一講。
2.使用案例
ViewModel類被設計用來存儲和管理界面相關的數(shù)據(jù),所以屏幕旋轉(zhuǎn)等配置更改前的數(shù)據(jù)可以被保存。像Activity和Fragment等應用組件由安卓系統(tǒng)管理它們的生命周期,基于一些用戶行為或用戶完全無法控制的設備事件來決定銷毀或重建它們。
既然上述對象由操作系統(tǒng)銷毀或重建,它們持有的任何數(shù)據(jù)都有可能丟失。例如,在Activity中顯示用戶數(shù)據(jù)的列表,當界面由于配置更改重建時,新的Activity需要重新獲取這些數(shù)據(jù)。對于簡單的數(shù)據(jù),Activity能通過onSaveInstanceState()
方法存儲,再從onCreate()
方法的Bundle參數(shù)中恢復數(shù)據(jù),但是這種方法只適用于像界面狀態(tài)這樣的少量數(shù)據(jù),不適用于像用戶列表這樣的大量數(shù)據(jù)。
另一個問題,這些UI Controller(Activity、Fragment等)經(jīng)常需要進行一些異步調(diào)用,可能會消耗一些時間來返回結(jié)果。這就導致必須管理這些調(diào)用,當UI Controller銷毀時清理它們,以避免潛在的內(nèi)存泄漏。這需要大量的維護,并且在配置更改導致重建對象的情況下,會再次發(fā)出相同的調(diào)用,這是浪費資源的。
還有個重要情況,這些UI Controller已經(jīng)需要響應用戶操作和處理操作系統(tǒng)的通信。若還需手動處理自己的資源,會導致類膨脹;即,一個類試圖自己處理整個應用的工作,而不是委派給其它類。這也導致測試更加困難。
將視圖數(shù)據(jù)的持有從UI Controller的邏輯中分離出來會使結(jié)構(gòu)更加簡單和高效。Lifecycles提供了ViewModel類,用來協(xié)助UI Controller,負責給界面準備數(shù)據(jù)。當配置改變時,它會自動持有數(shù)據(jù),這樣能立刻提供有效的數(shù)據(jù)給新的Activity或Fragment實例。拿上面的例子來說,應該由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
}
}
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
});
}
}
如果MyActivity被重建了,它將會接收到之前MyActivity創(chuàng)建的MyViewModel實例。當持有的Activity正常結(jié)束時,系統(tǒng)調(diào)用ViewModel的onCleared()
方法來釋放資源。
因為ViewModel的生命周期比對應的Activity和Fragment實例要長,所以它不應該引用一個View或任何一個可能持有Activity上下文引用的類。如果ViewModel需要Application的上下文(例如,去找到系統(tǒng)服務),它可以繼承AndroidViewModel類,并在構(gòu)造函數(shù)中接收Application(因為Application類繼承自Context類)。
3.在Fragment間共享數(shù)據(jù)
一個Activity中的兩個或多個Fragment需要相互通訊是種常見現(xiàn)象。但這種場景卻不簡單,所有的Fragment需要定義一些描述接口,并且所屬的Activity必須與它們綁定。此外,所有的Fragment都必須面對一種場景,那就是其它的Fragment還沒有創(chuàng)建或不可見。
上面的痛點可以使用ViewModel對象來解決。假設通過Fragment實現(xiàn)master-detail
場景,即用戶在一個Fragment的列表中選擇某一項,在另一個Fragment中展示選擇項的內(nèi)容。這些Fragment共享一個擁有Activity作用域的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;
}
}
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對象時,使用
getActivity()
方法。這意味著它們將會接收到相同的SharedViewModel實例,即作用域為Activity。
這樣做的好處有:
- Activity不需要做任何事情,或者知道關于此次通信的任何事情。
- 除了SharedViewModel類,F(xiàn)ragment不需要知道彼此相關的細節(jié)。如果其中一個Fragment消失了,其它的仍保持正常工作。
- 每個Fragment都有自己的生命周期,不會受到其它的生命周期的影響。實際上,界面中一個Fragment替換另一個,對界面工作不會有任何影響。
4.ViewModel的生命周期
ViewModel對象的作用域取決于,通過ViewModelProvider獲取ViewModel對象時,傳入的Lifecycle的生命周期范圍。它將一直呆在內(nèi)存中直到Lifecycle的生命周期永久地結(jié)束了—如果是Activity,當它finished;如果是Fragment,當它detached。
5.ViewModel與SavedInstanceState區(qū)別
當配置更改后,ViewModel提供了一種方便的途徑恢復數(shù)據(jù),但它不支持持久化。若應用被操作系統(tǒng)killed,將丟失數(shù)據(jù)。這時,可以使用系統(tǒng)早就提供的另一套機制,具體可以看這篇文章。
例如,用戶將應用放到后臺,幾小時后再切換回前臺,進程那時將被killed,并且安卓系統(tǒng)將從保存的狀態(tài)中恢復Activity。此過程中,開發(fā)者基本不需要做任何事情,因為所有的系統(tǒng)組件(View,Activity,F(xiàn)ragment)使用saved instance state
機制保存它們的狀態(tài)。可以在onSaveInstanceState()
回調(diào)方法中,給參數(shù)Bundle添加自定義數(shù)據(jù)。
通過onSaveInstanceState()
方法存儲的數(shù)據(jù)將保持在系統(tǒng)進程內(nèi)存中,但安卓系統(tǒng)只允許存儲很少的數(shù)據(jù),所以這不是保存應用實際數(shù)據(jù)的好地方。對于不能通過UI組件輕松地展示出來的東西需謹慎使用。
例如,如果有一個展示某國家信息的用戶界面,不應該將Country對象放入saved instance state
,應該將countryId放入存儲的狀態(tài)(除非已經(jīng)被View或Fragment的arguments
存儲)。實際對象應該保存在數(shù)據(jù)庫,并讓ViewModel能通過保存的countryId檢索到。
6.總結(jié)
到此,AAC最常用的功能基本介紹完了。至于Room Persistence Library
和Paging Library
,前者有大量可替代的庫,而后者不太穩(wěn)定,所以有時間再詳細介紹。由于現(xiàn)在AAC還沒有出穩(wěn)定版,大家可以多關注官網(wǎng)發(fā)布的更新進度,有問題也可以在這里反饋。