概覽
在考慮到UI 元素生命周期變化的環(huán)境下何之,架構(gòu)組件中的 ViewModel 主要用于存儲(chǔ)以及管理UI相關(guān)的數(shù)據(jù)。ViewModel 可以在配置信息變化(configuration change) 時(shí) (例如屏幕的旋轉(zhuǎn)) 保持?jǐn)?shù)據(jù)狀態(tài)咽筋。
眾所周知的是溶推,Android系統(tǒng)框架管理著UI控制器(Activity / Fragment)的生命周期。出于對(duì)用戶行為以及設(shè)備事件的響應(yīng)奸攻,系統(tǒng)可能會(huì)銷毀或重建UI控制器蒜危,而這些行為是完全不受控制的。如果系統(tǒng)銷毀 或是 重建UI控制器時(shí)睹耐,任何UI相關(guān)的暫存數(shù)據(jù)都將會(huì)被清除辐赞。
另一個(gè)問題是,UI控制器通常需要頻繁地調(diào)用異步接口硝训,耗費(fèi)一定的時(shí)間獲取數(shù)據(jù)响委。除了維護(hù)相關(guān)的接口外新思,在UI控制器被重新創(chuàng)建時(shí)往往會(huì)發(fā)起重復(fù)的請(qǐng)求,這無(wú)疑會(huì)造成資源的浪費(fèi)赘风。
UI 控制器如Activity / Fragment主要用于展示UI數(shù)據(jù)夹囚,響應(yīng)用戶請(qǐng)求,以及處理系統(tǒng)的事件邀窃,如授權(quán)訪問請(qǐng)求崔兴。在UI 控制器中加入請(qǐng)求加載數(shù)據(jù)庫(kù)或是網(wǎng)絡(luò)的數(shù)據(jù)的邏輯將使得類不斷膨脹。將大量無(wú)關(guān)的職責(zé)分配給UI 控制器蛔翅,而不是將各項(xiàng)職責(zé)分配出去則會(huì)導(dǎo)致出現(xiàn) God Object,同時(shí)會(huì)降低整體項(xiàng)目的可測(cè)試性位谋。
ViewModel 的生命周期 ( outlive Fragment or Activity ?)
且看官方給出的ViewModel生命周期圖示:
對(duì)照Demo中的實(shí)際效果:
利用ViewModel 在旋轉(zhuǎn)屏幕后山析,可以看到ViewModel的instance 還是同一個(gè),而且其中的score 數(shù)據(jù)依然保存了下來(lái)掏父。(觀察實(shí)際的log也可以印證這一點(diǎn))
如果直接按back 鍵退出笋轨,可以看到幾乎在Activity
的onDestroy()
方法調(diào)用的同時(shí),ViewModel#onCleared()
方法被調(diào)用赊淑,此時(shí)ViewModel也隨即銷毀了爵政。
顯而易見的是,除了數(shù)據(jù)已經(jīng)保存外陶缺,還有一個(gè)好處在于省去了需要手動(dòng)關(guān)聯(lián)/解除ViewModel
與界面生命周期的調(diào)用钾挟。即不必在先前自實(shí)現(xiàn)的ViewModel
|Presenter
中添加類似OnDestroy()
方法,并發(fā)起顯示的調(diào)用了饱岸。
實(shí)現(xiàn)
以上的ViewModel
中實(shí)現(xiàn)了‘自知’的生命周期掺出,這究竟是如何做到的呢?讓我們退一步回到在使用Fragment
時(shí)苫费,看如何在轉(zhuǎn)屏條件下保存內(nèi)存中的臨時(shí)數(shù)據(jù)汤锨。
從Fragment的角度來(lái)看狀態(tài)保存
在轉(zhuǎn)屏?xí)r,按默認(rèn)的情況百框,FragmentManager
會(huì)負(fù)責(zé)銷毀相關(guān)聯(lián)的Fragment
, Fragment
的生命周期 onPause
, onStop
, onDestroy
會(huì)被相繼調(diào)用闲礼。然而Fragment有一個(gè)特性可以設(shè)置Fragment#setRetainIntance(true)
保存當(dāng)前的自身實(shí)例(instance)。當(dāng)該選項(xiàng)設(shè)置后铐维,F(xiàn)ragment 當(dāng)前實(shí)例被保存下來(lái)了柬泽,然后隨即被傳給新的Activity。留存的Fragment實(shí)際利用了Fragment關(guān)聯(lián)的View可以被銷毀以及重新創(chuàng)建方椎,而不需要銷毀Fragment自身聂抢。
在發(fā)生configuration 改變的時(shí)候,Fragment
內(nèi)部的View與Activity
包含的View 一樣棠众,都會(huì)因?yàn)檫@一原因被銷毀以及重新創(chuàng)建琳疏。即當(dāng)有一個(gè)新的configuration變化時(shí)有决,很有可能需要新的資源,為了使用更好適配的資源空盼,View將會(huì)重新創(chuàng)建书幕。
隨后FragmentManager
會(huì)檢查每一個(gè)Fragment的retainInstance
屬性,如果值為默認(rèn)的false揽趾,FragmentManager
會(huì)將Fragment實(shí)例銷毀台汇,F(xiàn)ragment和它關(guān)聯(lián)的View都將會(huì)在新的Activity中重新創(chuàng)建;而如果retainInstance
值為true篱瞎,當(dāng)前Fragment
關(guān)聯(lián)的View將會(huì)銷毀而保留下Fragment的實(shí)例苟呐,在新的Activity創(chuàng)建時(shí),新的FragmentManager
會(huì)找到留存的Fragment俐筋,并重建它的View牵素。
留存的Fragment
沒有被銷毀,而只是從快要銷毀的Activity
上解綁了澄者。表示為retain
狀態(tài)的Fragment
仍然存在笆呆,只是不被任何Activity
持有。整個(gè)轉(zhuǎn)屏過(guò)程粱挡,對(duì)應(yīng)生命周期的變化可見下圖:
代碼尋蹤
按照上述Fragment實(shí)現(xiàn)的邏輯赠幕,架構(gòu)組件中的實(shí)現(xiàn)又是怎樣的呢?讓我們從源碼中尋找線索, 以Activity中的ViewModel創(chuàng)建為例询筏。
ViewModelProviders.of(activity).get(YourViewModel.class)
ViewModelProviders.of(activity)
-> new ViewModelProvider(ViewModelStores.of(activity), factory) // # factory 為默認(rèn)的 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); // 檢查是否已經(jīng)有緩存
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(); // 調(diào)用默認(rèn)構(gòu)造方法構(gòu)造實(shí)例
mViewModelStore.put(key, viewModel); // 添加ViewModel到緩存中
return (T) viewModel;
可以發(fā)現(xiàn)存儲(chǔ)的ViewModel
實(shí)際也是利用Fragment
設(shè)置為retained
的特性實(shí)現(xiàn)的榕堰。它的生命周期與內(nèi)置的不可見的HolderFragment
是緊密聯(lián)系的。在Fragment
最終被銷毀時(shí)屈留,ViewModel
的onCleared
用于執(zhí)行清理動(dòng)作的回調(diào)方法同時(shí)被觸發(fā)局冰。
與 onSaveInstanceState() 的異同
ViewModel保持的實(shí)際是暫存的UI中的數(shù)據(jù),但是它們并沒有被持久化(persisted)灌危。一旦相關(guān)的UI控制器被銷毀或是App進(jìn)程被暫停(由于系統(tǒng)資源的限制)康二,ViewModel和所包含的數(shù)據(jù)將被GC處理。
onSaveInstanceState()
該方法在以下2種情況下用來(lái)保留少量的UI相關(guān)數(shù)據(jù)勇蝙。
- App在后臺(tái)由于資源限制被暫停(stopped)
- 配置變化 (configuration change)
系統(tǒng)在UI層面已經(jīng)利用onSaveInstanceState()
去保存View的狀態(tài)信息(如 EditText中已輸入的文本, ListView中的滑動(dòng)位置等)沫勿。由于該方法在主線程中執(zhí)行,并且序列化本身會(huì)有一定的內(nèi)存開銷味混,即要求此方法能較快執(zhí)行产雹,以免出現(xiàn)UI掉幀或是更嚴(yán)重的性能的問題。所以它的設(shè)計(jì)并不適合大數(shù)據(jù)的存儲(chǔ)翁锡。
Fragment#setRetainInstance(true)
方法利用retained Fragment 實(shí)例蔓挖,可以保留較大規(guī)模的數(shù)據(jù) 如 bitmap
或是像網(wǎng)絡(luò)連接(network connections)這樣復(fù)雜的對(duì)象。
值得注意的是馆衔,ViewModel
只在由配置信息變化改變(configuration change)引起的銷毀過(guò)程中留存瘟判,當(dāng)進(jìn)程被暫停時(shí)怨绣,它也被銷毀了。
ViewModel 以及 Retained Fragment 測(cè)試源碼
Github: ArchComponentPlayground
其它ViewMode實(shí)現(xiàn)
https://github.com/inloop/AndroidViewModel