前言
學(xué)習(xí)記錄系列是通過(guò)閱讀學(xué)習(xí)《Android Jetpack應(yīng)用指南》對(duì)書(shū)中內(nèi)容學(xué)習(xí)記錄的Blog,《Android Jetpack應(yīng)用指南》京東天貓有售,本文是學(xué)習(xí)記錄的第四篇。
誕生
在頁(yè)面(Activity/Fragment)功能較為簡(jiǎn)單的情況下,通常會(huì)將UI交互、與數(shù)據(jù)獲取等相關(guān)的業(yè)務(wù)邏輯全部寫(xiě)在頁(yè)面中。但是在頁(yè)面功能復(fù)雜的情況下,這樣做是不合適的,因?yàn)樗环?strong>“單一功能原則”。頁(yè)面只應(yīng)該負(fù)責(zé)處理用戶與UI控件的交互,并將數(shù)據(jù)展示到屏幕上。與數(shù)據(jù)相關(guān)的業(yè)務(wù)邏輯應(yīng)該單獨(dú)處理和存放。
單一功能原則:在維基百科中關(guān)于“單一功能原則”的定義。在面向?qū)ο缶幊填I(lǐng)域中,單一功能原則(Single responsibility principle)規(guī)定每個(gè)類(lèi)都應(yīng)該有一個(gè)單一的功能,并且該功能應(yīng)該由這個(gè)類(lèi)完全封裝起來(lái)。這個(gè)類(lèi)的所有服務(wù)都應(yīng)該嚴(yán)密地和該功能平行(功能平行,意味著沒(méi)有依賴)
簡(jiǎn)介
ViewModel專門(mén)用于存放在應(yīng)用程序頁(yè)面所需的數(shù)據(jù)。ViewModel 是介于 View(視圖)和 Model(數(shù)據(jù)模型)之間的一個(gè)東西。它起到了橋梁的作用,使視圖和數(shù)據(jù)既能夠分離開(kāi),也能夠保持通信。
如圖所示,ViewModel將頁(yè)面所需的數(shù)據(jù)從頁(yè)面中剝離出來(lái),頁(yè)面只需要處理用戶交互和展示數(shù)據(jù)

ViewModel 的生命周期
ViewModel 生命周期是貫穿整個(gè) activity 生命周期,包括 Activity 因旋轉(zhuǎn)造成的重創(chuàng)建,直到 Activity 真正意義上銷(xiāo)毀后才會(huì)結(jié)束。既然如此,用來(lái)存放數(shù)據(jù)再好不過(guò)了。

ViewModel 的基本使用方法
1.在 app 的 build.gradle 中添加依賴。
dependencies {
添加ViewModel依賴
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'
}
2.寫(xiě)一個(gè)繼承自 ViewModel 的類(lèi),將其命名為 TimerViewModel
public class TimerViewModel extends ViewModel {
@Override
protected void onCleared() {
super.onCleared();
}
}
ViewModel 是一個(gè)抽象類(lèi),其中只有一個(gè) onCleared()方法。當(dāng) ViewModel 不再被需要,即與之相關(guān)的 Activity 都被銷(xiāo)毀時(shí), 該方法會(huì)被系統(tǒng)調(diào)用??梢栽谠摲椒ㄖ袌?zhí)行一些資源釋放的相關(guān)操作。注意,由于屏幕旋轉(zhuǎn)而導(dǎo)致的 Activity 重建,并不會(huì)調(diào)用該方法。
3.前面提到,ViewModel 最重要的作用時(shí)將視圖與數(shù)據(jù)分離,并獨(dú)立與 Activity 的重建。為了驗(yàn)證這一點(diǎn),在 ViewModel 中創(chuàng)建一個(gè)計(jì)時(shí)器 Timer,每隔 1s,通過(guò)接口 OnTimerChangeListener 通知它的調(diào)用者。
public class TimerViewModel extends ViewModel {
private Timer timer;
private int currentSecond;
/**
* ViewModel最重要的作用是將視圖與數(shù)據(jù)分離,并獨(dú)立于Activity的重建。
* 為了驗(yàn)證這樣一點(diǎn),在ViewModel中創(chuàng)建一個(gè)計(jì)時(shí)器Timer,每隔1s通過(guò)接口
* OnTimerChangeListener通知它的調(diào)用者
*/
public void startTiming() {
if (timer == null) {
currentSecond = 0;
timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
currentSecond ++;
if (onTimerChangeListener != null) {
onTimerChangeListener.onTimeChanged(currentSecond);
}
}
};
timer.schedule(timerTask, 1000, 1000);
}
}
/**
* 通過(guò)接口的方式完成對(duì)調(diào)用者的通知
*/
public interface OnTimerChangeListener {
void onTimeChanged(int currentSecond);
}
private OnTimerChangeListener onTimerChangeListener;
public void setOnTimerChangeListener(OnTimerChangeListener onTimerChangeListener) {
this.onTimerChangeListener = onTimerChangeListener;
}
/**
* ViewModel是一個(gè)抽象類(lèi),其中只有一個(gè)onCleared()方法。
* 當(dāng)ViewModel不再被需要,即與之相關(guān)的Activity都被銷(xiāo)毀時(shí),
* 該方法會(huì)被系統(tǒng)調(diào)用??梢栽谠摲椒ㄖ袌?zhí)行一些資源釋放相關(guān)操作
*/
@Override
protected void onCleared() {
super.onCleared();
timer.cancel();
}
}
4.在 TimerActivity 中監(jiān)聽(tīng) OnTimerChangeListener 發(fā)來(lái)的通知,并根據(jù)通知更新 UI 界面。ViewModel 的實(shí)例化過(guò)程,是通過(guò) ViewModelProvider 來(lái)完成的。ViewModelProvider 會(huì)判斷 ViewModel 是否存在,若存在則直接返回,否則它會(huì)創(chuàng)建一個(gè) ViewModel。
public class TimerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timer);
initComponent();
}
private void initComponent() {
final TextView tvTimer = findViewById(R.id.tv_timer);
// 實(shí)例化ViewModel
TimerViewModel testViewModel = new ViewModelProvider(this).get(TimerViewModel.class);
testViewModel.setOnTimerChangeListener(currentSecond -> {
// 更新UI界面
runOnUiThread(new Runnable() {
@Override
public void run() {
tvTimer.setText("Timer: " + currentSecond);
}
});
});
testViewModel.startTiming();
}
}
運(yùn)行程序并旋轉(zhuǎn)屏幕,當(dāng)旋轉(zhuǎn)屏幕導(dǎo)致 Activity 重建時(shí),計(jì)時(shí)器沒(méi)有停止。這意味著在橫/豎屏狀態(tài)下的 Activity 所對(duì)應(yīng)的 ViewModel 是同一個(gè),它并沒(méi)有被銷(xiāo)毀,它所持有的數(shù)據(jù)也一直到存在著。
ViewModel 的原理
在頁(yè)面中通過(guò) ViewModelProvider 類(lèi)來(lái)實(shí)例化 ViewMdeol
TestViewModel testViewModel = new ViewModelProvider(this).get(TestViewModel.class);
ViewModelPrivider 接收一個(gè) ViewModelStoreOwner 對(duì)象作為參數(shù)。在以上示例代碼中該參數(shù)是 this ,指代當(dāng)前的 Activity。這是因?yàn)?Activity 繼承自 FragmentActivity,而在 androidx 依賴包中,F(xiàn)ragmentActivity 默認(rèn)實(shí)現(xiàn) ViewModelStoreOwner 接口。
public class FragmentActivity extends ComponentActivity implements
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompat.RequestPermissionsRequestCodeValidator {
...
@NonNull
@Override
public ViewModelStore getViewModelStore() {
return FragmentActivity.this.getViewModelStore();
}
...
}
接口方法 getViewModelStore() 所定義的返回類(lèi)型為 ViewModelStore。
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
從 ViewModelStore 的源碼可以看出,ViewModel 實(shí)際是以 HashMap<String,ViewModel>的形式被緩存起來(lái)了。ViewModel 與頁(yè)面之間沒(méi)有直接的關(guān)聯(lián),它們通過(guò) ViewModelProvider 進(jìn)行關(guān)聯(lián)。當(dāng)頁(yè)面需要 ViewModel 時(shí),會(huì)向 ViewModelProvider 索要,ViewModelProvider 檢查該 ViewModel 是否已經(jīng)存在于緩存中,若存在,則直接返回,若不存在,則實(shí)例化一個(gè)。因此,Activity 由于配置變化導(dǎo)致的銷(xiāo)毀重建并不會(huì)影響 ViewModel ,ViewModel 是獨(dú)立于頁(yè)面存在的。也正因?yàn)榇耍?strong>在使用 ViewModel 時(shí)需要特別注意,不需要向 ViewModel 中傳入任何類(lèi)型的 Context 或 帶有 Context 引用的對(duì)象,這可能會(huì)導(dǎo)致頁(yè)面無(wú)法被銷(xiāo)毀,從而引發(fā)內(nèi)存泄漏。
需要注意的是,除了 Activity,androidx 依賴包中的 Fragment 也默認(rèn)實(shí)現(xiàn)了 ViewModelStoreOwner 接口。因此,也可以在 Fragment 中正常使用 ViewModel。
ViewModel 與 AndroidViewModel
ViewModel 中不能將任何類(lèi)型和 Context 或 含有 Context引用的對(duì)象傳入到 ViewModel 中,因?yàn)檫@可能會(huì)導(dǎo)致內(nèi)存泄漏。如果希望在 ViewModel 中使用 Context,可以使用 AndroidViewModel 類(lèi),它繼承自 ViewModel,并接收 Application 作為 Context。這意味著,它的生命周期和 Application 是一樣的,那么這就不算是一個(gè)內(nèi)存泄漏了。
ViewModel 與 onSaveInstanceState()方法
1.onSaveInstanceState()方法只能保存少量的、能支持序列化的數(shù)據(jù)。ViewModel沒(méi)有這個(gè)限制
2.ViewModel 能支持頁(yè)面中所有的數(shù)據(jù)。ViewModel 不支持?jǐn)?shù)據(jù)的持久化,當(dāng)頁(yè)面被徹底銷(xiāo)毀時(shí),ViewModel 及持有的數(shù)據(jù)就不存在了。onSaveInstanceState()方法可以持久化頁(yè)面的數(shù)據(jù)。
3.二者不可混淆
總結(jié)
ViewModel 可以幫助我們更好地將頁(yè)面與數(shù)據(jù)從代碼層間上分離開(kāi)來(lái)。更重要的是,依賴于 ViewModel 的生命周期特性,我們不再需要關(guān)心屏幕旋轉(zhuǎn)帶來(lái)的數(shù)據(jù)丟失的問(wèn)題,進(jìn)而也不需要重新獲取數(shù)據(jù)。
需要注意的是,在使用 ViewModel 的過(guò)程中,千萬(wàn)不要將任何類(lèi)型的 Context 或 含有 Context引用的對(duì)象傳入到 ViewModel ,這可能會(huì)引起內(nèi)存泄漏。如果一定要在 ViewModel 中使用 Context,那么建議使用 ViewModel 的子類(lèi) AndroidViewModel。