android mvvm livedata_再谈Android应用架构——Jetpack VS 生命周期
前面我們對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的核心就三個類:Lifecycle、LifecycleOwner和LifecycleObserver。從名字就可以輕易看出這是一個觀察者模式,Activity作為LifecycleOwner,把生命周期的變化反映到Lifecycle,Lifecycle再通知給所有的LifecycleObserver即可。這個概念太簡單了,就不在此贅述源碼了(看了一下沒有什么亮點~),不過如果你感興趣,請注意一下ReportFragment這個類,Activity的生命周期就是通過它來通知Lifecycle的(添加一個看不見的Fragment,這個操作似乎似曾相識?)。
Lifecycle只是讓P和VM獲得了生命周期感知能力,并沒有解決如何保持的問題,不過它是我們后面內容的基礎,所以還是很有必要了解一番。
ViewModel
這個ViewModel其實就是MVVM中的VM,但經過Google的加工之后具備了很好的生命周期感知能力,這就是我們苦苦追尋的東西呀。現在我們就對它抽絲剝繭,看看系統是如何完成這件事的。
ViewModel的使用非常簡單,就是一句話:
class?LoginActivity?:?AppCompatActivity()?{????private?lateinit?var?loginViewModel:?LoginViewModel
????override?fun?onCreate(savedInstanceState:?Bundle?)?{
????????super.onCreate(savedInstanceState)
????????//?...
????????loginViewModel?=?ViewModelProvider(this,?LoginViewModelFactory()).get(LoginViewModel::class.java)
????}
}
當重建發生時,LoginActivity、ViewModelProvider都是新的實例,但是LoginViewModel一定得是原來的實例,這說明它在某處被緩存了起來。先看下ViewModelProvider做了什么吧:
public?ViewModelProvider(@NonNull?ViewModelStoreOwner?owner,?@NonNull?Factory?factory)?{????this(owner.getViewModelStore(),?factory);
}
public??T?get(@NonNull?Class?modelClass)?{
????String?canonicalName?=?modelClass.getCanonicalName();//?...return?get(DEFAULT_KEY?+?":"?+?canonicalName,?modelClass);
}public??T?get(@NonNull?String?key,?@NonNull?Class?modelClass)?{
????ViewModel?viewModel?=?mViewModelStore.get(key);if?(modelClass.isInstance(viewModel))?{//?...return?(T)?viewModel;
????}?else?{//?...???
????}if?(mFactory?instanceof?KeyedFactory)?{
????????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,它的實現如下:
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)?{
????????????//?Restore?the?ViewModelStore?from?NonConfigurationInstances
????????????mViewModelStore?=?nc.viewModelStore;
????????}
????????if?(mViewModelStore?==?null)?{
????????????mViewModelStore?=?new?ViewModelStore();
????????}
????}
????return?mViewModelStore;
}
這里出現了一個getLastNonConfigurationInstance(),我們在前面提過一個getLastCustomNonConfigurationInstance()方法,那么應該也有一個onRetainNonConfigurationInstance()與之對應,它的實現如下:
public?final?Object?onRetainNonConfigurationInstance()?{????Object?custom?=?onRetainCustomNonConfigurationInstance();
????ViewModelStore?viewModelStore?=?mViewModelStore;
????if?(viewModelStore?==?null)?{
????????//?No?one?called?getViewModelStore(),?so?see?if?there?was?an?existing
????????//?ViewModelStore?from?our?last?NonConfigurationInstance
????????NonConfigurationInstances?nc?=
????????????????(NonConfigurationInstances)?getLastNonConfigurationInstance();
????????if?(nc?!=?null)?{
????????????viewModelStore?=?nc.viewModelStore;
????????}
????}
????if?(viewModelStore?==?null?&&?custom?==?null)?{
????????return?null;
????}
????NonConfigurationInstances?nci?=?new?NonConfigurationInstances();
????nci.custom?=?custom;
????nci.viewModelStore?=?viewModelStore;
????return?nci;
}
一切都很明了了,系統用的是和我們一樣的方法,只是方法名稱稍有區別而已。ViewModelStore里緩存了ViewModel實例,那么在Activity真正銷毀時肯定需要清空,ViewModel和ViewModelStore都提供了一個clear()方法,ViewModelStore的clear方法實現如下:
public?final?void?clear()?{????for?(ViewModel?vm?:?mMap.values())?{
????????vm.clear();
????}
????mMap.clear();
}
它會調用其中每個ViewModel的clear方法使我們有機會清除一些數據或任務,然后就將Map清空了。在Activity中它是這樣被調用的:
getLifecycle().addObserver(new?LifecycleEventObserver()?{????@Overridepublic?void?onStateChanged(@NonNull?LifecycleOwner?source,
????????????@NonNull?Lifecycle.Event?event)?{
????????if?(event?==?Lifecycle.Event.ON_DESTROY)?{
????????????if?(!isChangingConfigurations())?{
????????????????getViewModelStore().clear();
????????????}
????????}
????}
});
isChangingConfigurations()用以標識Activity執行onDestory方法后是否準備重建,只有不重建時才會清空ViewModel,所以只要在clear時清理數據和中斷任務就好了。
現在我們解決了ViewModel實例保持的問題,接下來讓我們再想想應該怎么解決數據重復加載的問題。數據重復加載主要是因為一個異步任務被多次調用,例如請求一個列表數據時,如果屏幕發生旋轉,以下方法會被多次調用:
fun?getUsers(){????executor.execute?{
????????val?users?=?model.getUsers()
????????handler.post{
????????????view?.getUsers(users)
????????}
????}
}
按照之前的說法,可以給加載任務加上標記,當它正在加載中就等待它加載完成,如果已經加載完就取緩存的數據,但是這太復雜了,稍有不慎就會出問題。如何讓事情變得簡單一些,出錯率低一些呢?
要想避免此問題,最好的方式是只調用一次getUsers()方法,那這個方法就不能由Activity來調用了,需要ViewModel自己調用,等它拿到結果后反過來通知Activity。這不就是MVVM嗎?現在我們總算明白為什么被系統實現的這個類叫ViewModel了,因為它就是為MVVM量身定制的。
關于數據反過來通知Activity這件事,也不需要擔心,因為系統照樣幫我們實現了,這就是LiveData。
LiveData
可觀察的數據并不是只有LiveData,但LiveData有自己獨特的本領,它也具備生命周期感知力。LiveData只有在有效的生命周期范圍內通知觀察者,并在生命周期結束后自動移除觀察者,僅這一點就足夠讓它脫穎而出。我們可以從它的observe方法,了解它處理生命周期的大致流程。
public?void?observe(@NonNull?LifecycleOwner?owner,?@NonNull?Observer?super?T>?observer)?{????assertMainThread("observe");
????if?(owner.getLifecycle().getCurrentState()?==?DESTROYED)?{
????????//?ignore
????????return;
????}
????LifecycleBoundObserver?wrapper?=?new?LifecycleBoundObserver(owner,?observer);
????ObserverWrapper?existing?=?mObservers.putIfAbsent(observer,?wrapper);
????if?(existing?!=?null?&&?!existing.isAttachedTo(owner))?{
????????throw?new?IllegalArgumentException("Cannot?add?the?same?observer"
????????????????+?"?with?different?lifecycles");
????}
????if?(existing?!=?null)?{
????????return;
????}
????owner.getLifecycle().addObserver(wrapper);
}
這里創建了一個LifecycleBoundObserver來觀察Activity的生命周期,我們看看它做了哪些工作吧:
class?LifecycleBoundObserver?extends?ObserverWrapper?implements?LifecycleEventObserver?{????//?...
????@Override
????boolean?shouldBeActive()?{
????????return?mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
????}
????@Override
????public?void?onStateChanged(@NonNull?LifecycleOwner?source,
????????@NonNull?Lifecycle.Event?event)?{
????????if?(mOwner.getLifecycle().getCurrentState()?==?DESTROYED)?{
????????????removeObserver(mObserver);
????????????return;
????????}
????????activeStateChanged(shouldBeActive());
????}
????//?...
}
它實現了LifecycleEventObserver,并在DESTROYED狀態時移除了觀察者,其后只是調用了一個activeStateChanged方法,這個方法實現如下:
void?activeStateChanged(boolean?newActive)?{????if?(newActive?==?mActive)?{
????????return;
????}
????//?immediately?set?active?state,?so?we'd?never?dispatch?anything?to?inactive
????//?owner
????mActive?=?newActive;
????boolean?wasInactive?=?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應用架構
更多文章正在火速連載中,感謝您的關注!
掃描一下二維碼就可以關注哦
總結
以上是生活随笔為你收集整理的android mvvm livedata_再谈Android应用架构——Jetpack VS 生命周期的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js表格中将每行的两个数据求和显示_py
- 下一篇: python2.7.3怎么安装_pyth