1. 程式人生 > 其它 >android mvvm livedata_再談Android應用架構——Jetpack VS 生命週期

android mvvm livedata_再談Android應用架構——Jetpack VS 生命週期

技術標籤:android mvvm livedataandroid 生命週期android生命週期

前面我們對MVC、MVP、MVVM進行了詳盡的分析,但還有一個問題懸而未決,那就是生命週期。在Android平臺上生命週期具有十分重要的意義,因此這也是架構必須考慮的因素之一。生命週期處理不好很容易發生記憶體洩漏,但對架構而言,真正困擾我們的卻不是記憶體洩漏的問題,反而是因生命週期太短,例項被銷燬重建,從而產生一系列不必要的行為。這種情況發生的場景主要在螢幕旋轉以及頁面被系統回收時。

Activity難免需要依賴網路、資料庫等資料來渲染頁面,當螢幕旋轉時,Activity重建,因而資料需要重新載入,但這完全沒有必要。一種策略是對資料進行快取,這是一種可考慮的方案,但它只解決了一半的問題,如果Activity重建發生在資料返回前,此時根本來不及快取,下一次請求就迅速地發生了。

在MVP、MVVM架構中,資料由M來提供,但真正和生命週期打交道的是P和VM,我們得從這裡著手解決生命週期的問題。再明確地說一遍,我們要解決的問題是不論在資料返回前還是返回後,在螢幕旋轉這種場景下都不需要多次載入資料。這個問題由兩種狀態組成:載入中和載入完成後,對於前者我們要知道當前正在載入資料,對於後者則只需要把資料快取起來即可。

對資料快取很簡單,但載入中的狀態就要好好斟酌一番了,我們可以輕易地給這個狀態加標記,但隨著重建這個標記也會被回收,由此可以想到兩種應對之法,一是讓P和VM不被回收,這樣就可以進行標記了,二是讓當前這個載入不被回收,也就是其生命週期不和P與VM同步。不讓P和VM回收,有以下幾種方式:

  • 配置android:configChanges="orientation|keyboardHidden|screenSize"

  • onRetainCustomNonConfigurationInstance()/getLastCustomNonConfigurationInstance()

  • 繼承Fragment

除了配置configChanges,其餘兩種方式都是不錯的解決辦法。除此之外還有一種方式可以同時實現我們說的兩種應對之法,這就是Loader。關於什麼是Loader以及Loader如何保持P和VM不被回收,大家可以自行查閱相關資料,如何保持一個載入任務不被回收,可以參閱architecture-samples

(https://github.com/android/architecture-samples),並切換到分支deprecated-todo-mvp-loaders

我們不打算大刀闊斧地講述每個方案的細節和優缺點,因為隨著Jetpack誕生,這種複雜又費力的方案系統已經幫我們完成了,我們只需要瞭解系統是如何處理的即可。從書寫程式碼變成檢視程式碼,可以說大大減少了我們對生命週期的“怨恨”,不得不說Google這波操作很圈粉呢。在這裡,我們只關注Lifecycle、ViewModel和LiveData三部分。

Lifecycle

生命週期讓人困擾的很大一部分原因是隻有Activity這樣的系統元件才可以感知生命週期的變化,而Lifecycle的出現則把這種感知力放大到了任何類。Lifecycle的原理很簡單,當生命週期變化時,Activity通知到Lifecycle,其他類就可以通過Lifecycle感知生命週期的變化了。

Lifecycle的核心就三個類:LifecycleLifecycleOwnerLifecycleObserver。從名字就可以輕易看出這是一個觀察者模式,Activity作為LifecycleOwner,把生命週期的變化反映到Lifecycle,Lifecycle再通知給所有的LifecycleObserver即可。這個概念太簡單了,就不在此贅述原始碼了(看了一下沒有什麼亮點~),不過如果你感興趣,請注意一下ReportFragment這個類,Activity的生命週期就是通過它來通知Lifecycle的(新增一個看不見的Fragment,這個操作似乎似曾相識?)。

Lifecycle只是讓P和VM獲得了生命週期感知能力,並沒有解決如何保持的問題,不過它是我們後面內容的基礎,所以還是很有必要了解一番。

ViewModel

這個ViewModel其實就是MVVM中的VM,但經過Google的加工之後具備了很好的生命週期感知能力,這就是我們苦苦追尋的東西呀。現在我們就對它抽絲剝繭,看看系統是如何完成這件事的。

ViewModel的使用非常簡單,就是一句話:

classLoginActivity:AppCompatActivity(){

privatelateinitvarloginViewModel:LoginViewModel

overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
//...
loginViewModel=ViewModelProvider(this,LoginViewModelFactory()).get(LoginViewModel::class.java)
}
}

當重建發生時,LoginActivity、ViewModelProvider都是新的例項,但是LoginViewModel一定得是原來的例項,這說明它在某處被快取了起來。先看下ViewModelProvider做了什麼吧:

publicViewModelProvider(@NonNullViewModelStoreOwnerowner,@NonNullFactoryfactory){
this(owner.getViewModelStore(),factory);
}

publicTget(@NonNullClassmodelClass){
StringcanonicalName=modelClass.getCanonicalName();//...returnget(DEFAULT_KEY+":"+canonicalName,modelClass);
}publicTget(@NonNullStringkey,@NonNullClassmodelClass){
ViewModelviewModel=mViewModelStore.get(key);if(modelClass.isInstance(viewModel)){//...return(T)viewModel;
}else{//...
}if(mFactoryinstanceofKeyedFactory){
viewModel=((KeyedFactory)(mFactory)).create(key,modelClass);
}else{
viewModel=(mFactory).create(modelClass);
}
mViewModelStore.put(key,viewModel);return(T)viewModel;
}

非常簡單,從Activity獲取到了一個ViewModelStore,如果裡面包含了LoginViewModel就直接取出來,否則新建一個並快取到ViewModelStore裡。那麼ViewModelStore是什麼,它是如何保持下來的?

ViewModelStore裡維護了一個Map,儲存ViewModel例項,僅此而已。AppCompatActivity實現了ViewModelStoreOwner介面,裡面只有一個方法getViewModelStore,它的實現如下:

publicViewModelStoregetViewModelStore(){
if(getApplication()==null){
thrownewIllegalStateException("Youractivityisnotyetattachedtothe"
+"Applicationinstance.Youcan'trequestViewModelbeforeonCreatecall.");
}
if(mViewModelStore==null){
NonConfigurationInstancesnc=
(NonConfigurationInstances)getLastNonConfigurationInstance();
if(nc!=null){
//RestoretheViewModelStorefromNonConfigurationInstances
mViewModelStore=nc.viewModelStore;
}
if(mViewModelStore==null){
mViewModelStore=newViewModelStore();
}
}
returnmViewModelStore;
}

這裡出現了一個getLastNonConfigurationInstance(),我們在前面提過一個getLastCustomNonConfigurationInstance()方法,那麼應該也有一個onRetainNonConfigurationInstance()與之對應,它的實現如下:

publicfinalObjectonRetainNonConfigurationInstance(){
Objectcustom=onRetainCustomNonConfigurationInstance();

ViewModelStoreviewModelStore=mViewModelStore;
if(viewModelStore==null){
//NoonecalledgetViewModelStore(),soseeiftherewasanexisting
//ViewModelStorefromourlastNonConfigurationInstance
NonConfigurationInstancesnc=
(NonConfigurationInstances)getLastNonConfigurationInstance();
if(nc!=null){
viewModelStore=nc.viewModelStore;
}
}

if(viewModelStore==null&&custom==null){
returnnull;
}

NonConfigurationInstancesnci=newNonConfigurationInstances();
nci.custom=custom;
nci.viewModelStore=viewModelStore;
returnnci;
}

一切都很明瞭了,系統用的是和我們一樣的方法,只是方法名稱稍有區別而已。ViewModelStore裡快取了ViewModel例項,那麼在Activity真正銷燬時肯定需要清空,ViewModel和ViewModelStore都提供了一個clear()方法,ViewModelStore的clear方法實現如下:

publicfinalvoidclear(){
for(ViewModelvm:mMap.values()){
vm.clear();
}
mMap.clear();
}

它會呼叫其中每個ViewModel的clear方法使我們有機會清除一些資料或任務,然後就將Map清空了。在Activity中它是這樣被呼叫的:

getLifecycle().addObserver(newLifecycleEventObserver(){
@OverridepublicvoidonStateChanged(@NonNullLifecycleOwnersource,
@NonNullLifecycle.Eventevent){
if(event==Lifecycle.Event.ON_DESTROY){
if(!isChangingConfigurations()){
getViewModelStore().clear();
}
}
}
});

isChangingConfigurations()用以標識Activity執行onDestory方法後是否準備重建,只有不重建時才會清空ViewModel,所以只要在clear時清理資料和中斷任務就好了。

現在我們解決了ViewModel例項保持的問題,接下來讓我們再想想應該怎麼解決資料重複載入的問題。資料重複載入主要是因為一個非同步任務被多次呼叫,例如請求一個列表資料時,如果螢幕發生旋轉,以下方法會被多次呼叫:

fungetUsers(){
executor.execute{
valusers=model.getUsers()
handler.post{
view?.getUsers(users)
}
}
}

按照之前的說法,可以給載入任務加上標記,當它正在載入中就等待它載入完成,如果已經載入完就取快取的資料,但是這太複雜了,稍有不慎就會出問題。如何讓事情變得簡單一些,出錯率低一些呢?

要想避免此問題,最好的方式是隻呼叫一次getUsers()方法,那這個方法就不能由Activity來呼叫了,需要ViewModel自己呼叫,等它拿到結果後反過來通知Activity。這不就是MVVM嗎?現在我們總算明白為什麼被系統實現的這個類叫ViewModel了,因為它就是為MVVM量身定製的。

關於資料反過來通知Activity這件事,也不需要擔心,因為系統照樣幫我們實現了,這就是LiveData。

LiveData

可觀察的資料並不是只有LiveData,但LiveData有自己獨特的本領,它也具備生命週期感知力。LiveData只有在有效的生命週期範圍內通知觀察者,並在生命週期結束後自動移除觀察者,僅這一點就足夠讓它脫穎而出。我們可以從它的observe方法,瞭解它處理生命週期的大致流程。

publicvoidobserve(@NonNullLifecycleOwnerowner,@NonNullObserversuperT>observer){
assertMainThread("observe");
if(owner.getLifecycle().getCurrentState()==DESTROYED){
//ignore
return;
}
LifecycleBoundObserverwrapper=newLifecycleBoundObserver(owner,observer);
ObserverWrapperexisting=mObservers.putIfAbsent(observer,wrapper);
if(existing!=null&&!existing.isAttachedTo(owner)){
thrownewIllegalArgumentException("Cannotaddthesameobserver"
+"withdifferentlifecycles");
}
if(existing!=null){
return;
}
owner.getLifecycle().addObserver(wrapper);
}

這裡建立了一個LifecycleBoundObserver來觀察Activity的生命週期,我們看看它做了哪些工作吧:

classLifecycleBoundObserverextendsObserverWrapperimplementsLifecycleEventObserver{
//...

@Override
booleanshouldBeActive(){
returnmOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}

@Override
publicvoidonStateChanged(@NonNullLifecycleOwnersource,
@NonNullLifecycle.Eventevent){
if(mOwner.getLifecycle().getCurrentState()==DESTROYED){
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}

//...
}

它實現了LifecycleEventObserver,並在DESTROYED狀態時移除了觀察者,其後只是呼叫了一個activeStateChanged方法,這個方法實現如下:

voidactiveStateChanged(booleannewActive){
if(newActive==mActive){
return;
}
//immediatelysetactivestate,sowe'dneverdispatchanythingtoinactive
//owner
mActive=newActive;
booleanwasInactive=LiveData.this.mActiveCount==0;
LiveData.this.mActiveCount+=mActive?1:-1;
if(wasInactive&&mActive){
onActive();
}
if(LiveData.this.mActiveCount==0&&!mActive){
onInactive();
}
if(mActive){
dispatchingValue(this);
}
}

這裡通過是否active來分發資料,在dispatchingValue中會通知所有的觀察者。

LiveData實際上是一個雙層的觀察者模式,它通過觀察Lifecycle得知是否active,在此充當的是觀察者。當它的值發生變化或者監聽到Lifecycle變化時再通知到它的觀察者,在此又充當被觀察者。如此它就具備了我們想要的一切能力。

總結

現在我們的架構“本地化”工作又前進了一大步,它終於在生命週期方面也不存在問題了,使用Lifecycle+ViewModel+LiveData組合,解決了架構最棘手的問題,也把MVVM推向了另一個高度。當然這並不代表著MVP就徹底敗下陣來,畢竟生命週期問題隻影響了初始化時的資料,大量場景下還是有無數的互動行為,需要根據使用者的操作主動載入各種各樣的資料,這種情況下,MVP的直觀性要遠遠強於MVVM,這個特點也可以簡單理解為MVP適合複雜互動場景,MVVM適合展示型場景。因此我們應該根據具體場景靈活選用MVP和MVVM,甚至在某些情況下可以合二為一。

還是那句話,沒有最好的架構,只有最適合當前場景的架構。

往期精彩

回顧

Git實用指南完結篇

排序演算法(一)

Java集合原始碼分析之Map(四):HashMap

也談Android應用架構

更多文章正在火速連載中,感謝您的關注!

640?wx_fmt=jpeg

掃描一下二維碼就可以關注哦