一个典型问题:ViewModel如何在界面重建中保存数据?-灵析社区

江江说技术

众所周知,ViewModel可以在界面销毁重建后仍然保存之前的数据,而到底是怎么在界面销毁重建期间进行保存的呢,本篇文章就就该问题进行一个探究。

由于ViewModel能够在界面销毁重建时保存数据,那我们就从Activity销毁的时机作为入口一探究竟。

AMS通知ApplicationThread执行界面销毁

应用执行界面销毁是通过AMS通过Binder跨进程通知应用这边的ApplicationThread这个Binder对象,而ApplicationThread通过Handler最终会执行到ActivityThread.handleDestroyActivity()方法。

而这个方法又会调用performDestroyActivity()方法,我们看下源码:

  1. retainNonConfigurationInstances()就是ViewModel能在界面销毁重建后保存数据的关键方法,稍后会进行详细分析;
  2. Instrumentation.callActivityOnDestroy()这个方法最终就会回调大家熟悉的Activity.onDestroy()方法;

Activity.retainNonConfigurationInstances()瞧一瞧

NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    //...
    return nci;
}

紧接着就会调用onRetainNonConfigurationInstance()方法,ComponentActivity会对这个方法进行重写:

public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        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;
}

这个方法值得细细分析一下,首先先搞清楚mViewModelStore是个啥:

看到这里是不是明白了:mViewModelStore是个ViewModelStore类型,而我们在Activity界面创建的ViewModel就会保存到这个对象之中

到了这里,我们就可以知道:ActivityThread.handleDestroyActivity()最终会一步步走到mViewModelStore,将其进行保存。

接下来我们就看下这个值mViewModelStore经过一步步调用是怎么保存的 。

mViewModelStore如何一步步调用保存?

回到我们的方法onRetainNonConfigurationInstance()中,从源码中可以看到,mViewModelStore最终会保存到ComponentActivity$NonConfigurationInstances类的viewModelStore成员属性中,并返回ComponentActivity$NonConfigurationInstances对象。

简单看下NonConfigurationInstances类结构:

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

我们跳到调用onRetainNonConfigurationInstance()方法的Activity.retainNonConfigurationInstances()方法中:

可以看到上面的onRetainNonConfigurationInstance()方法返回的ComponentActivity$NonConfigurationInstances对象最终会保存到Activity$NonConfigurationInstances类的activity成员变量中。

请注意,别搞混了NonConfigurationInstances类,AcitivityComponentActivity都有定义这个类,我们看下Acitivity定义的NonConfigurationInstances结构:

static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}

最终Activity.retainNonConfigurationInstances()方法会将Activity$NonConfigurationInstances类对象返回到上一层。

最终我们又回到了performDestroyActivity()方法中:

最终Activity$NonConfigurationInstances会保存到ActivityClientRecordlastNonConfigurationInstances属性中。

而这个ActivityClientRecord是保存到ActivityThreadmActivities集合中,其中key就是token,value就是为ActivityClientRecord

界面重建怎么恢复数据呢?

界面重新创建销毁后,AMS会通知ApplicationThread最终调用到performLaunchActivity()方法。

这个方法就是用来创建Activity的,创建完毕后就会调用我们熟悉的Activity.attach()方法:

可以看到这个方法传递的参数其中之一就是ActivityClientRecordlastNonConfigurationInstances属性,继续深入看下Activity.attach()方法:

final void attach(//...NonConfigurationInstances lastNonConfigurationInstances,//...) {
    attachBaseContext(context);
    //...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    //...
}

最终这个lastNonConfigurationInstances会赋值给AcitivtymLastNonConfigurationInstances属性。

mLastNonConfigurationInstances就是Activity$NonConfigurationInstances对象,就保存了之前存储ViewModelViewModelStore,这就间接实现了保存了ViewModel中持有的数据。

ViewModel的获取流程

我们看下如何在Activity中创建一个ViewModel

private val mViewModel: MainViewModel by viewModels()

关键就是viewModels()方法:

最终ViewModel会尝试从viewModelStore中获取,获取不到通过反射创建。而viewModelStore是从哪里来的呢?

可以看到,最终这个viewModelStore最终就是从上面的Activity.mLastNonConfigurationInstances属性中获取。

总结

本篇文章我们详细分析ViewModel如何实现在Activity界面销毁重建后还能够保存销毁前的数据的,希望对你有所帮助。

阅读量:1075

点赞量:0

收藏量:0