上一篇文章中我們介紹了Architecture Components中的LifeCycle,LifeCycleOwner及LifeCycleObserver朴乖,不知道大家掌握的怎么樣吸耿?在學(xué)習(xí)編碼的路上氓癌,還是要多多實踐才可以呢绒障。
接下來我們要介紹的是ViewModel番舆。
ViewModel簡介
ViewModel是用來存儲和管理生命周期過程敏感的界面數(shù)據(jù)的一個類酝碳,用ViewModel存儲的數(shù)據(jù)可以在應(yīng)用設(shè)置項發(fā)生改變時保存下來例如當(dāng)屏幕旋轉(zhuǎn)。
Android框架層管理UI控件的生命周期比如Activity和Fragment恨狈∈杌框架層需要決定在面對用戶交互時何時銷毀或者重建UI控件,這一過程不是由開發(fā)者控制的禾怠。
如果系統(tǒng)銷毀活著重建UI控件沃斤,那么用戶的輸入數(shù)據(jù)活著你已緩存的UI數(shù)據(jù)都會丟失,例如刃宵,在你的應(yīng)用中有一個Activity展示著一個用戶列表衡瓶,當(dāng)屏幕發(fā)生旋轉(zhuǎn)時,Activity被重建牲证,那么新創(chuàng)建的Activity需要再去請求一次用戶列表數(shù)據(jù)哮针。對于一些簡單的數(shù)據(jù),我們可以使用onSavedInstanceState()方法存儲在Bundle中坦袍,然后在onCreate()函數(shù)中恢復(fù)十厢,但是這種情況只適用于少量并且可以被序列化的數(shù)據(jù),并不適用于其他數(shù)據(jù)捂齐,例如說一個用戶列表或者很多圖片蛮放。
另一個問題是UI控件需要頻繁的發(fā)起異步請求并等待返回結(jié)果。UI控件需要去管理這些異步請求并保證在它完成后被系統(tǒng)清理掉以避免內(nèi)存泄漏奠宜。這種管理需要大量的耐心和細(xì)心包颁,并且在這些對象因為設(shè)置改變而重建的情形下,造成了一種資源浪費(fèi)压真。
Activity和Fragment這種UI空間只是去展示UI數(shù)據(jù)娩嚼,響應(yīng)用戶交互或者處理系統(tǒng)交互,例如說請求權(quán)限滴肿,UI控件同時也需要負(fù)責(zé)從數(shù)據(jù)庫或者網(wǎng)絡(luò)上加載數(shù)據(jù)岳悟,我們應(yīng)該進(jìn)行責(zé)任分?jǐn)偅灰獮閁I控件添加過多的操作泼差,這樣會導(dǎo)致一個類去處理一個應(yīng)用所要處理的工作贵少,讓我們的測試工作變得更加艱難。
綜合以上幾點(diǎn)堆缘,Google推出了ViewModel滔灶,用于幫助UI控件準(zhǔn)備數(shù)據(jù),ViewModel會在設(shè)置發(fā)生變化時自動存儲數(shù)據(jù)套啤,他們所持有的數(shù)據(jù)可以立刻被新建的Fragment或者Activity復(fù)用宽气。
ViewModel的使用
假設(shè)我們需要在一個 應(yīng)用頁面內(nèi)展示一個用戶列表随常,那么我們可以將數(shù)據(jù)請求操作托管給ViewModel,代碼如下:
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 an asyncronous operation to fetch users.
}
}
實現(xiàn)一個ViewModel只需要繼承自ViewModel即可萄涯,隨后我們可以在Activity中訪問數(shù)據(jù)绪氛,方式如下:
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對象涝影,當(dāng)持有該ViewModel對象的Activity銷毀時枣察,系統(tǒng)會調(diào)用ViewModel.onCleared()方法釋放資源。
注意ViewModel對象絕不能持有View燃逻,LifeCycle或者任何持有Activity 引用的對象
在設(shè)計理念上序目,ViewModel的生命周期獨(dú)立于View或者LifeCycleOwner,這種設(shè)計也意味著你可以更簡單的為ViewModel編寫測試用例以覆蓋ViewModel中的操作伯襟。ViewModel可以持有LifeCycleObservers比如說LiveData猿涨,如果ViewModel需要使用Application的引用,例如說去獲取一個系統(tǒng)服務(wù)姆怪,此時可以繼承自AndroidViewModel叛赚,該類有一個構(gòu)造函數(shù),可以接受Application的引用稽揭。
這里我再舉一個簡單的ViewModel的例子俺附,以便大家更好的理解ViewModel。這個例子是界面上有一個Button和一個TextView溪掀,TextView用于記錄Button 的點(diǎn)擊次數(shù)事镣,在屏幕旋轉(zhuǎn)時保持TextView上的次數(shù)不變。
首先編寫布局文件揪胃,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.code.archicomponentssmaples.MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:text="Click Me"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
這里我試用了約束布局璃哟,不懂得同學(xué)可以自行百度,或者使用LinearLayout/RelativeLayout自己實現(xiàn)即可只嚣。
編寫ViewModel類沮稚,用于持有Button的點(diǎn)擊次數(shù),如下:
public class ClickCounterViewModal extends ViewModel {
private int count = 0;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
隨后在Activity中使用該ViewModel緩存Button點(diǎn)擊次數(shù)册舞,代碼如下:
public class LifeOwnerActivity extends AppCompatActivity {
private TextView mTextView;
private Button mButton;
private ClickCounterViewModal mClickCounterViewModal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_life_owner);
mClickCounterViewModal = ViewModelProviders.of(this).get(ClickCounterViewModal.class);
initView();
initData();
}
private void initView(){
mTextView = (TextView)findViewById(R.id.owner_textView);
mButton = findViewById(R.id.owner_Button);
}
private void initData(){
displayClickCount();
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mClickCounterViewModal.setCount(mClickCounterViewModal.getCount()+1);
displayClickCount();
}
});
}
private void displayClickCount(){
mTextView.setText(mClickCounterViewModal.getCount()+"");
}
}
如上述代碼ViewModel對象需要使用ViewModelProviders.of(Context)
進(jìn)行初始化。
另外多說一點(diǎn)ViewModel的提出只是為了處理UI相關(guān)數(shù)據(jù)的緩存問題障般,并不代表它能完全替代onSavedInstanceState()的作用调鲸。
在這個例子中ViewModel的工作流程如下圖:
ViewModel生命周期
ViewModel對象的生命周期依賴于初始化時ViewModelProvider傳入的LifeCycle對象,ViewModel對象會常駐在內(nèi)存中直到與其對應(yīng)的LifeCycle被銷毀挽荡,對于Activity而言藐石,就是當(dāng)其被finish時,對于Fragment而言就是當(dāng)它被detach的時候定拟。
下圖說明了一個Activity在發(fā)生屏幕旋轉(zhuǎn)時自身的生命周期變化以及與其對應(yīng)的ViewModel的生命周期于微。
通常情況下逗嫡,在System調(diào)起Activity時,我們在Activity的onCreate()函數(shù)內(nèi)初始化ViewModel對象株依,隨后系統(tǒng)可能多次調(diào)用該Activity的onCreate()函數(shù)驱证,例如說發(fā)生多次屏幕旋轉(zhuǎn)。這種清醒下ViewModel來源于第一次onCreate(),直到調(diào)用Activity.finish()時恋腕,該ViewModel對象才會被銷毀并釋放資源抹锄。
使用ViewModel在Fragment間共享數(shù)據(jù)
在我們?nèi)粘W兂缮钪校珹ctivity需要與其內(nèi)部的一個或多個Fragment交互信息的需求比比皆是荠藤,假設(shè)又這樣一種情形伙单,我們有一個Activity頁面,左右各一個Fragment哈肖,左側(cè)展示列表吻育,右側(cè)展示列表中某一項的詳情。這種情形下Fragment中需要定義接口淤井,持有Fragment的Activity需要同時綁定這兩個Fragment扫沼,并且一個Fragment要處理另一個Fragment沒有創(chuàng)建或顯示的問題。
這種痛點(diǎn)可以通過ViewModel解決庄吼,這兩個Fragment可以公用一個ViewModel對象缎除,在Activity內(nèi)處理,示例代碼如下:
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.
});
}
}
如上所示总寻,兩個Fragment只需要監(jiān)聽ViewModel中值的變化更新UI即可器罐。
需要注意的是這兩個Fragment在ViewModelProvider內(nèi)傳入了getActivity(), 此時該ViewModel對象的生命周期完全依賴于持有兩個Fragment的Activity。
這種模式有如下幾點(diǎn)好處:
- Activity不需要關(guān)注交互中的任何事渐行;
- Fragments彼此不需要知道對方的狀態(tài)轰坊,一個Fragment消失了,另一個仍然可以正常工作祟印;
- 每個Fragment有其獨(dú)立的生命周期肴沫,不會彼此影響,如果一個Fragment被另一個Fragment替換了蕴忆,UI仍然能正常顯示颤芬;
使用ViewModel代替Loaders
類似于CursorLoader的加載類經(jīng)常被頻繁的用于異步維護(hù)界面和數(shù)據(jù)庫的數(shù)據(jù)一致,現(xiàn)在你也可以用ViewModel和一些其他的輔助類來實現(xiàn)這種功能了套鹅,使用ViewModel可以使我們的界面控制與數(shù)據(jù)加載解耦站蝠。
一種常見的使用CursorLoader監(jiān)聽數(shù)據(jù)庫變化的結(jié)構(gòu)圖如下,當(dāng)一個數(shù)據(jù)庫值發(fā)生改變時卓鹿,加載器會自動觸發(fā)一個重新加載事件菱魔,完成后更新UI。
當(dāng)ViewModel與Room以及LiveData結(jié)合使用時吟孙,就可以完全替代加載器的功能澜倦,ViewModel保證數(shù)據(jù)在設(shè)置發(fā)生改變的過程中不清空聚蝶,當(dāng)數(shù)據(jù)發(fā)生改變時,Room通知LiveData藻治,隨后使用新的數(shù)據(jù)更新UI碘勉。
當(dāng)數(shù)據(jù)變得越來越復(fù)雜時,你或許會使用一個單獨(dú)的類去加載數(shù)據(jù)栋艳,ViewModel的目的就在于保證數(shù)據(jù)在生命周期變化過程中不被清空恰聘,關(guān)于LiveData,Room相關(guān)的更多詳細(xì)內(nèi)容吸占,我們會在下一篇推文中詳細(xì)介紹晴叨,感謝大家閱讀。更多詳細(xì)內(nèi)容歡迎大家關(guān)注我的公眾號
[圖片上傳失敗...(image-feea9f-1512483891026)]