Jetpack是Google I / O 2017 中引入, 其中的架構組件 Android Architecture Components , 就是我們常說的AAC. 包括Databinding, Lifecycles, LiveData, ViewModel, Navigation, Paging, Room, WorkManager組件. 每個組件都是獨立的,你可以使用一個或者組合使用.
這里介紹的是 ViewModel 組件, 主要分為 :
- ViewModel的作用
- ViewModel對ConfigChange的抵抗性實現(xiàn)原理
- ViewModel和View層的通信
- ViewModel的銷毀
1. 作用
在介紹 ViewModel 的作用前, 先看看 ViewModel 類和 MVVM 中的 VM.
1.1 ViewModel層和ViewModel類
在 AAC-ViewModel 出現(xiàn)之前, 我們已經(jīng)在 MVVM 架構模式中使用過 ViewModel 層, 可以參考官方的 todo-mvvm-rxjava 項目.
MVVM中的 ViewModel 作為層級, 擔任分離 View(視圖) 和 Model(數(shù)據(jù)模型)的職責.
ViewModel類是 Jetpack 架構組件的一部分, 他的實現(xiàn)可以扮演MVVM中的 ViewModel 層, 但兩者并不是一個東西, 前者是分離數(shù)據(jù)/視圖的邏輯層, 后者的官方定義是 :
以注重生命周期的方式管理界面相關的數(shù)據(jù). ViewModel類旨在以生命周期有意識的方式存儲和管理ui相關的數(shù)據(jù). ViewModel類允許數(shù)據(jù)在配置更改(如屏幕旋轉(zhuǎn))之后存活.
參考 ConfigChange 時的生命周期圖 :
Activity 經(jīng)歷了創(chuàng)建頁面, 銷毀頁面, 重建頁面的過程, 而 ViewModelScope(ViewModel的集合容器) 在創(chuàng)建后, 無視銷毀/重建的過程.

借住這個特性, 將 Model 保存在 ViewModel 中, 能幫助我們快速恢復 ConfigChange 重建后的頁面狀態(tài).
1.2 保存UI的狀態(tài)
跨系統(tǒng)發(fā)起的活動或應用程序破壞及時保存和恢復活動的UI狀態(tài)是用戶體驗的一個重要部分。在這些情況下,用戶希望UI狀態(tài)保持不變,但是系統(tǒng)會銷毀活動和其中存儲的任何狀態(tài).
當用戶對UI狀態(tài)的期望與默認系統(tǒng)行為不匹配時,必須保存并恢復用戶的UI狀態(tài),以確保系統(tǒng)啟動的銷毀對用戶是透明的.
在大多數(shù)情況下,應該同時使用ViewModel和onSaveInstanceState().
Save/Restore InstanceState
系統(tǒng)會為每個打開的APP分配獨立的進程, 但是內(nèi)存資源并不是無限的, 在資源不足的情況下系統(tǒng)會回收進程. 根據(jù)被回收的優(yōu)先級, 進程分為前臺進程,可見進程,服務進程,緩存進程.
當應用的進程被回收后, Activity實例和其中保存的對象都會被回收. 當用戶重啟應用后, 會創(chuàng)建全新的Activity實例, 之前頁面上的數(shù)據(jù)丟失. 這時我們就要借助安卓提供的保存/恢復機制 onSaveInstanceState(bundle) .
參考官網(wǎng)對 onSaveInstanceState(bundle) 的描述: 當 Activity 處于可能被回收的狀態(tài), 系統(tǒng)就會調(diào)用 onSaveInstanceState(bundle) 保存數(shù)據(jù)供我們將來重新打開 Activity 時恢復界面使用.
This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state
configuration changes
運行時發(fā)生變化(例如屏幕方向、鍵盤可用性,以及當用戶啟用[多窗口模式])時,
Android 會重啟正在運行的 Activity(先后調(diào)用 onDestroy() 和 onCreate()). 重啟行為旨在通過利用與新設備配置相匹配的備用資源來自動重新加載您的應用,從而幫助它適應新配置。
如果重啟 Activity 需要恢復大量數(shù)據(jù)、重新建立網(wǎng)絡連接或執(zhí)行其他密集操作,那么因配置變更而引起的完全重啟可能會給用戶留下應用運行緩慢的體驗。這時我們就需要使用 ViewModel 來幫助我們進行重啟后的數(shù)據(jù)恢復. 因為 ViewModel 是在內(nèi)存中的, 從內(nèi)存中獲取重建前保存的 ViewModel 過程非???
兩者的關系
已經(jīng)有了
onSaveInstanceState(bundle), 為什么還需要 ViewModel ?
onSaveInstanceState(bundle)是通過 Bundle 存儲數(shù)據(jù)的, 最終會通過Binder做一個跨進程的通信將 Bundle 的數(shù)據(jù)傳遞給 ActivityManager , 而安卓中Binder緩沖區(qū) 參考官方文檔 只有1MB, 只能存儲輕量級的數(shù)據(jù). 并且 Bundle 的序列化/反序列化會帶來額外的開銷.
而 ViewModel 在ConfigChange 前后一直存在于內(nèi)存中(參考生命周期圖), 重建后可以從內(nèi)存中直接獲取 ViewModel, 沒有任何額外的開銷.ViewModel 能代替
onSaveInstanceState(bundle)嗎?
ViewModel 只能用于 ConfigChange 后數(shù)據(jù)的恢復. 如果是進程被回收的情況, ViewModel 也會隨著頁面的銷毀而銷毀. 這時候必須借助onSaveInstanceState(bundle)機制來重建.
所以 ViewModel 和onSaveInstanceState(bundle)應該同時存在.
舉個例子
我們需要通過 userId 來請求 userInfo , 在 onSaveInstanceState(bundle) 將 userId 保存.
- 正常情況下我們在 ViewModel 中根據(jù)
userId來請求userInfo - 在
onSaveInstanceState(bundle)保存userId而不是復雜的userInfo. 當進程可能被回收時, 調(diào)用該方法. - 如果是進程被回收后再次打開的頁面重建, 此時ViewModel 也是全新的對象. 這時我們可以獲取
onSaveInstanceState(bundle)中保存的userId, 請求userInfo交給 ViewModel 完成. - 如果是 ConfigChange 導致的頁面重建, 此時 ViewModel 并沒有被銷毀,
userInfo依然存在. 這時onSaveInstanceState(bundle)里的數(shù)據(jù)仍然會被傳遞給 ViewModel, 只需要在 ViewModel 里判斷是否已經(jīng)維護了數(shù)據(jù), 直接返回之前的userInfo即可.
偽代碼如下 :
class DetailActivity : BaseActivity<ActivityDetailBinding>() {
private val viewModel by lazy { getInjectViewModel<DetailViewModel>() }
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
outState?.putInt("id", userId) //保存userId
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
//獲取userId
viewModel.setUserId(savedInstanceState?.getInt("id"))
}
}
class DetailViewModel{
private val _userId = MutableLiveData<Int>()
fun setUserId(userId: Int?) {
//ConfigChange重建后, Activity還會調(diào)用這個方法, 這時沒必要再次請求.
if (_userId.value == userId) {
return
}
_userId.value = userId
}
val userInfo: LiveData<Resources<DetailModel>> = Transformations.switchMap(_userId) { topicId ->
if (topicId == null) {
AbsentLiveData.create()
} else {
getUserInfo(_userId) //獲取userInfo
}
}
}
2. 源碼分析
2.1 創(chuàng)建ViewModel
創(chuàng)建ViewModel的過程分為兩步
val detailViewModel = ViewModelProviders.of(this@DetailActivity).get(DetailViewModel::class.java)
第一步 ViewModelProviders.of() 有四個重載方法

以 ViewModelProviders.of(activity) 為例, 返回創(chuàng)建的 ViewModelProvider 對象
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
//Application非空判斷
Application application = checkApplication(activity);
if (factory == null) { //factory為空就指定一個默認的factory
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
//創(chuàng)建ViewModelProvider
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
第二步會調(diào)用 ViewModelProvider#get(modelClass) 根據(jù) ViewMdoel.class 對象獲取 ViewModel
public class ViewModelProvider {
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
//獲得class的規(guī)范名稱, 可以理解為包名
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
//獲取ViewModel
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//mViewModelStore是個hashMap, 用ViewModel的包名作為key查找hashMap中是否已經(jīng)添加了ViewModel.
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) { //如果存在ViewModel,直接返回
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
//根據(jù)ViewModel的class創(chuàng)建 ViewModel
viewModel = mFactory.create(modelClass);
// 新建的ViewModel會添加到mViewModelStore的hashMap中
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}
首先嘗試從 ViewModelStore 中獲取 ViewModel, 如果沒有就通過 viewModel = mFactory.create(modelClass) 創(chuàng)建并添加到 ViewModelStore 中.
ViewModelStore 內(nèi)部維護了一個 HashMap<ViewModel包名, ViewModel> 存儲Activity的所有ViewModel.
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);
}
}
既然 ViewModel 是通過 ViewModelStore 存取的, 那么 ViewModel 在重建后仍然存在也和 ViewModelStore 相關.
2.2 ViewModelStore
SDK中使用了大量的面向接口編程, ViewModelStore 也是通過接口獲取的. Fragment/FragmentActivity 都實現(xiàn)了該接口.
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
以 FragmentActivity 的 getViewModelStore() 實現(xiàn)為例, 會嘗試從 NonConfigurationInstances 中獲取 mViewModelStore.
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// 從NonConfigurationInstances中獲取viewModelStore
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
要了解重建后 ViewModel 的來源, 所以我們只需要繼續(xù)看重建后 NonConfigurationInstances 的來源.
2.3 NonConfigurationInstances
Activity 的生命周期都是通過 ActivityThread 管理的, 重建Activity通過 ActivityThread # handleRelaunchActivity 實現(xiàn).
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
public void handleRelaunchActivity(ActivityClientRecord tmp,
PendingTransactionActions pendingActions) {
....
ActivityClientRecord r = mActivities.get(tmp.token);
....
handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
....
}
ActivityThread # handleRelaunchActivityInner() 分為兩步 :
- handleDestroyActivity()銷毀當前的頁面
- handleLaunchActivity()重建新的頁面
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
PendingTransactionActions pendingActions, boolean startsNotResumed,
Configuration overrideConfig, String reason) {
...
//銷毀頁面
handleDestroyActivity(r.token, false, configChanges, true, reason);
...
//啟動頁面
handleLaunchActivity(r, pendingActions, customIntent);
...
}
1. ActivityThread # handleDestroyActivity()
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
configChanges, getNonConfigInstance, reason);
}
在 performDestroyActivity() 在調(diào)用 mInstrumentation.callActivityOnDestroy(r.activity) 也就是銷毀 Activity 之前, 會調(diào)用 activity.retainNonConfigurationInstances() 將返回的 NonConfigurationInstances 對象保存到 ActivityClientRecord 中.
/** Core implementation of activity destroy call. */
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
//獲取當前Activity對應的 ActivityClientRecord
ActivityClientRecord r = mActivities.get(token);
....
if (getNonConfigInstance) {
try {
//在銷毀前保存 NonConfigurationInstances
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to retain activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
}
}
}
//調(diào)用Activity.onDestory()
mInstrumentation.callActivityOnDestroy(r.activity);
....
}
``
在 Activity # retainNonConfigurationInstances() 中調(diào)用 onRetainNonConfigurationInstance() 保存 NonConfigurationInstance, 但是 Activity 自己的實現(xiàn)為null, 肯定是交給子類 FragmentActivity 實現(xiàn)了.
public class Activity
NonConfigurationInstances retainNonConfigurationInstances() {
//這一步保存NonConfigurationInstance
Object activity = onRetainNonConfigurationInstance();
....
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity; //用變量activity來保存
...
return nci;
}
public Object onRetainNonConfigurationInstance() {
return null;
}
查看 FragmentActivity 的 onRetainNonConfigurationInstance() 實現(xiàn), 靜態(tài)內(nèi)部類 FragmentActivity.NonConfigurationInstances 保存了 mViewModelStore .
@Override
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = mViewModelStore;
nci.fragments = fragments;
return nci;
}
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
FragmentManagerNonConfig fragments;
}
2.ActivityThread # handleLaunchActivity()
handleLaunchActivity(ActivityClientRecord r) 和 handleDestroyActivity(ActivityClientRecord r) 中的是同一個.
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
....
final Activity a = performLaunchActivity(r, customIntent);
....
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent){
....
//調(diào)用Activity.attach() 保存record變量
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
//調(diào)用Activity的onCreate()
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
....
}
在 Activity.attach() 中保存 ActivityRecord 的屬性, 包括lastNonConfigurationInstances并提供 getLastNonConfigurationChildInstances() 方法
public class Activity extends ContextThemeWrapper
final void attach(....){
...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
...
}
HashMap<String, Object> getLastNonConfigurationChildInstances() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.children : null;
}
}
到 FragmentActivity.onCreate() 中, 從 nc.viewModelStore 中獲得 ViewModelStore.
public class FragmentActivity extends ComponentActivity implements
ViewModelStoreOwner
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState);
//獲得 NonConfigurationInstances 對象, 里面記錄了銷毀前保存的 viewModelStore
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
FragmentManagerNonConfig fragments;
}
}
通過上面的分析可以知道 ViewModel 在ConfigChange時的緩存是通過ActivityRecord實現(xiàn)的 :
ActivityClientRecord r 中保存了 Activity.NonConfigurationInstances.
Activity.NonConfigurationInstances 中保存了 FragmentActivity.NonConfigurationInstances.
FragmentActivity.NonConfigurationInstances 中保存了 ViewModelStore viewModelStore
ViewModelStore viewModelStore 內(nèi)部通過HashMap保存了 當前Activity的所有 ViewModel
銷毀頁面時, ViewModel 最終將被保存到 ActivityRecord 中.
重建頁面時, 使用了同一個 ActivityRecord 來進行數(shù)據(jù)的恢復, 從中可以獲得銷毀前頁面上所有 ViewModel 的容器 ViewModelStore, 再次調(diào)用 ViewModelProviders.of(Activity.class).get(ViewModel.class) 根據(jù)類名從 HashMap 中獲得已經(jīng)創(chuàng)建過的 ViewModel.
3. 和視圖層的通信
由于ViewModel類實現(xiàn)了對 ConfigChange 抵抗性, 更加適合作為MVVM中的 ViewModel 層使用.
我們使用 MVP 和 MVVM 時, 都是為了進行視圖層 View 和模型 Model 的分離. 所以Model和View的通信需要我們自己實現(xiàn).
- 在 MVP 中, Presenter使用定義好的接口和View進行交互.
- 在 MVVM 中沒有提供這樣的接口, ViewModel可以借助DataBinding或者觀察者模式將Model的變更通知到View層.
Databinding
使用 DataBinding 庫將 Model 綁定到 View. Dtabinding庫還支持雙向綁定, View 層的變化也會影響到 Model.
使用觀察者模式
以 ViewModel 中使用 LiveData 為例 :
- 使用 ViewModelProvider 在 View層(Activity或者Fragment) 中創(chuàng)建 ViewModel.
- ViewModel 中通過 Repository 從Db(AAC-Room或者其他ORM)或者network獲取數(shù)據(jù), 使用 LiveData 保存這些數(shù)據(jù).
- 在 View 層中觀察 LiveData<Model> 來進行視圖的響應.

4. ViewModel的銷毀
4.1 ConfigChange引發(fā)的視圖銷毀
當ConfigChange發(fā)生時, ViewModel 的生命周期明顯超出了 View層 的生命周期. 所以 ViewModel 不能直接或間接持有 View 的引用:
包括Activity/Fragment和包含他們引用的對象Dialog, SnackBar等等.
一個通用的法則是,你的 ViewModel 中沒有導入像 android.這樣的包(像 android.arch. 這樣的AAC組件除外)。這個經(jīng)驗也同樣適用于 MVP 模式中的 Presenter .
4.2 正常的視圖銷毀
當頁面正常銷毀而非ConfigChange引起的銷毀時, ViewMdoel 對象應當被正?;厥?
在 ViewModel 內(nèi)我們進行了大量獲取 Model 的操作, 例如請求網(wǎng)絡,讀取數(shù)據(jù)庫,文件IO等等. 而 ViewModel 又無法感知 View 的生命周期, 所以 ViewModel 提供了 onCleared() 抽象方法作為釋放資源的時機.
public class FragmentActivity extends ComponentActivity {
@Override
protected void onDestroy() {
super.onDestroy();
//頁面銷毀時候調(diào)用ViewModelStore.clear()
if (mViewModelStore != null && !isChangingConfigurations()) {
mViewModelStore.clear();
}
mFragments.dispatchDestroy();
}
}
public class ViewModelStore {
public final void clear() {
//遍歷所有ViewModel的onCleared()方法
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
public abstract class ViewModel {
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
}
我們可以在這里釋放資源,例如取消訂閱, 防止造成頁面的泄露或者崩潰異常.
class DetailViewModel: ViewModel()
override fun onCleared() {
super.onCleared()
//通知repository取消訂閱
repository.unbindViewModel()
}
}
class DetailRepository{
public fun unbindViewModel(){
//取消訂閱
mSubscription.unsubscribe()
}
}