前面我们对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的使用非常简单,就是一句话:

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);
}

public<TextendsViewModel>Tget(@NonNullClass<T>modelClass){
StringcanonicalName=modelClass.getCanonicalName();
//...
returnget(DEFAULT_KEY+":"+canonicalName,modelClass);
}

public<TextendsViewModel>Tget(@NonNullStringkey,@NonNullClass<T>modelClass){
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(){
@Override
publicvoidonStateChanged(@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,@NonNullObserver<?superT>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==;
LiveData.this.mActiveCount+=mActive?1:-1;
if(wasInactive&&mActive){
onActive();
}
if(LiveData.this.mActiveCount==&&!mActive){
onInactive();
}
if(mActive){
dispatchingValue(this);
}
}

这里通过是否active来分发数据,在dispatchingValue中会通知所有的观察者。

LiveData实际上是一个双层的观察者模式,它通过观察Lifecycle得知是否active,在此充当的是观察者。当它的值发生变化或者监听到Lifecycle变化时再通知到它的观察者,在此又充当被观察者。QQ号码购买如此它就具备了我们想要的一切能力。


总结

现在我们的架构“本地化”工作又前进了一大步,它终于在生命周期方面也不存在问题了,使用Lifecycle+ViewModel+LiveData组合,解决了架构最棘手的问题,也把MVVM推向了另一个高度。当然这并不代表着MVP就彻底败下阵来,毕竟生命周期问题只影响了初始化时的数据,大量场景下还是有无数的交互行为,需要根据用户的操作主动加载各种各样的数据,这种情况下,MVP的直观性要远远强于MVVM,这个特点也可以简单理解为MVP适合复杂交互场景,MVVM适合展示型场景。因此我们应该根据具体场景灵活选用MVP和MVVM,甚至在某些情况下可以合二为一。

还是那句话,没有最好的架构,只有最适合当前场景的架构。


更多相关文章

  1. 数据库mysql——MySQL 性能优化的最佳20多条经验分享
  2. json数据类型,安装MySQL
  3. PHP变量声明及数据类型
  4. json 数据类型、时间与时间戳
  5. 关于数据库的一些常识
  6. linux上mongodb的安装与卸载
  7. 数据库--存储过程详解
  8. 怎么样优化数据库语句?
  9. PHP用数据库类实现数据库增删改查

随机推荐

  1. android中的线程池
  2. 收藏Android下bitmap内存限制OUT OF MEMO
  3. ADT在线安装(http://dl-ssl.google.com/an
  4. android ImageView src属性不支持.9图
  5. android之interpolator的用法详解
  6. Android 动画的重复播放
  7. Android中常用的五种布局
  8. Android MMS,SMS之常用Uri
  9. Android SDK 下载 链接
  10. Android获取视频音频的时长的方法