概覽
在考慮到UI 元素生命周期變化的環境下,架構組件中的 ViewModel 主要用于存儲以及管理UI相關的數據。ViewModel 可以在配置信息變化(configuration change) 時 (例如屏幕的旋轉) 保持數據狀態。
眾所周知的是,Android系統框架管理著UI控制器(Activity / Fragment)的生命周期。出于對用戶行為以及設備事件的響應,系統可能會銷毀或重建UI控制器,而這些行為是完全不受控制的。如果系統銷毀 或是 重建UI控制器時,任何UI相關的暫存數據都將會被清除。
另一個問題是,UI控制器通常需要頻繁地調用異步接口,耗費一定的時間獲取數據。除了維護相關的接口外,在UI控制器被重新創建時往往會發起重復的請求,這無疑會造成資源的浪費。
UI 控制器如Activity / Fragment主要用于展示UI數據,響應用戶請求,以及處理系統的事件,如授權訪問請求。在UI 控制器中加入請求加載數據庫或是網絡的數據的邏輯將使得類不斷膨脹。將大量無關的職責分配給UI 控制器,而不是將各項職責分配出去則會導致出現 God Object,同時會降低整體項目的可測試性。
ViewModel 的生命周期 ( outlive Fragment or Activity ?)
且看官方給出的ViewModel生命周期圖示:
對照Demo中的實際效果:
利用ViewModel 在旋轉屏幕后,可以看到ViewModel的instance 還是同一個,而且其中的score 數據依然保存了下來。(觀察實際的log也可以印證這一點)
如果直接按back 鍵退出,可以看到幾乎在Activity
的onDestroy()
方法調用的同時,ViewModel#onCleared()
方法被調用,此時ViewModel也隨即銷毀了。
顯而易見的是,除了數據已經保存外,還有一個好處在于省去了需要手動關聯/解除ViewModel
與界面生命周期的調用。即不必在先前自實現的ViewModel
|Presenter
中添加類似OnDestroy()
方法,并發起顯示的調用了。
實現
以上的ViewModel
中實現了‘自知’的生命周期,這究竟是如何做到的呢?讓我們退一步回到在使用Fragment
時,看如何在轉屏條件下保存內存中的臨時數據。
從Fragment的角度來看狀態保存
在轉屏時,按默認的情況,FragmentManager
會負責銷毀相關聯的Fragment
, Fragment
的生命周期 onPause
, onStop
, onDestroy
會被相繼調用。然而Fragment有一個特性可以設置Fragment#setRetainIntance(true)
保存當前的自身實例(instance)。當該選項設置后,Fragment 當前實例被保存下來了,然后隨即被傳給新的Activity。留存的Fragment實際利用了Fragment關聯的View可以被銷毀以及重新創建,而不需要銷毀Fragment自身。
在發生configuration 改變的時候,Fragment
內部的View與Activity
包含的View 一樣,都會因為這一原因被銷毀以及重新創建。即當有一個新的configuration變化時,很有可能需要新的資源,為了使用更好適配的資源,View將會重新創建。
隨后FragmentManager
會檢查每一個Fragment的retainInstance
屬性,如果值為默認的false,FragmentManager
會將Fragment實例銷毀,Fragment和它關聯的View都將會在新的Activity中重新創建;而如果retainInstance
值為true,當前Fragment
關聯的View將會銷毀而保留下Fragment的實例,在新的Activity創建時,新的FragmentManager
會找到留存的Fragment,并重建它的View。
留存的Fragment
沒有被銷毀,而只是從快要銷毀的Activity
上解綁了。表示為retain
狀態的Fragment
仍然存在,只是不被任何Activity
持有。整個轉屏過程,對應生命周期的變化可見下圖:
代碼尋蹤
按照上述Fragment實現的邏輯,架構組件中的實現又是怎樣的呢?讓我們從源碼中尋找線索, 以Activity中的ViewModel創建為例。
ViewModelProviders.of(activity).get(YourViewModel.class)
ViewModelProviders.of(activity)
-> new ViewModelProvider(ViewModelStores.of(activity), factory) // # factory 為默認的 AndroidViewModelFactory
ViewModelStores.of(activity)
-> ViewModelStore
HolderFragment#holderFragmentFor(activity).getViewModelStore()
HolderFragmentManager#holderFragmentFor(activity)
FragmentManager fm = activity.getSupportFragmentManager();
HolderFragment holder = fm.findFragmentByTag(HOLDER_TAG)
if (holder != null) {
return holder;
}
holder = new HolderFragment();
public HolderFragment() {
setRetainInstance(true); // retained!
}
public void onDestroy()
super.onDestroy();
mViewModelStore.clear();
for (ViewModel vm : mMap.values()) {
vm.onCleared(); // notifies ViewModels that they are no longer used.
}
fm.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
holder.getViewModelStore()
viewModelProvider.get(modelClass) //YourViewModel.class
get(key, modelClass)
ViewModel viewModel = mViewModelStore.get(key); // 檢查是否已經有緩存
if (modelClass.isInstance(viewModel)) {
return (T) viewModel
}
viewModel = mFactory.create(modelClass);
AndroidViewModelFactory#create(modelClass)
if(AndroidViewModel.class.isAssignableFrom(modelClass)) // 檢查是否為AndroidViewModel類別
return modelClass.getConstructor(Application.class).newInstance(mApplication);
super.create(modelClass)
NewInstanceFactory#create(modelClass)
return modelClass.newInstance(); // 調用默認構造方法構造實例
mViewModelStore.put(key, viewModel); // 添加ViewModel到緩存中
return (T) viewModel;
可以發現存儲的ViewModel
實際也是利用Fragment
設置為retained
的特性實現的。它的生命周期與內置的不可見的HolderFragment
是緊密聯系的。在Fragment
最終被銷毀時,ViewModel
的onCleared
用于執行清理動作的回調方法同時被觸發。
與 onSaveInstanceState() 的異同
ViewModel保持的實際是暫存的UI中的數據,但是它們并沒有被持久化(persisted)。一旦相關的UI控制器被銷毀或是App進程被暫停(由于系統資源的限制),ViewModel和所包含的數據將被GC處理。
onSaveInstanceState()
該方法在以下2種情況下用來保留少量的UI相關數據。
- App在后臺由于資源限制被暫停(stopped)
- 配置變化 (configuration change)
系統在UI層面已經利用onSaveInstanceState()
去保存View的狀態信息(如 EditText中已輸入的文本, ListView中的滑動位置等)。由于該方法在主線程中執行,并且序列化本身會有一定的內存開銷,即要求此方法能較快執行,以免出現UI掉幀或是更嚴重的性能的問題。所以它的設計并不適合大數據的存儲。
Fragment#setRetainInstance(true)
方法利用retained Fragment 實例,可以保留較大規模的數據 如 bitmap
或是像網絡連接(network connections)這樣復雜的對象。
值得注意的是,ViewModel
只在由配置信息變化改變(configuration change)引起的銷毀過程中留存,當進程被暫停時,它也被銷毀了。
ViewModel 以及 Retained Fragment 測試源碼
Github: ArchComponentPlayground
其它ViewMode實現
https://github.com/inloop/AndroidViewModel