In combination with the previous article Android Interview Summary – ViewModel We know:

Called when the configuration changes Activity# onRetainNonConfigurationInstance () to save to save the ViewModel object mViewModelStore sample, And call getViewModelStore() after the Activity is rebuilt, Which invokes the ensureViewModelStore () in its internal will call getLastNonConfigurationInstance () method to obtain ViewModelStore object cache, if it returns, If no, create a new ViewModelStore instance.

This article mainly look after configuration changes, how to invoke Activity# onRetainNonConfigurationInstance ().

Debug breakpoints to know that the ActivityThread#handleRelaunchActivity method is called after a configuration change

Why do you call ActivityThread#handleRelaunchActivity?

    @Override
    public void handleRelaunchActivity(ActivityClientRecord tmp,
            PendingTransactionActions pendingActions) {
        ...
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity"); .Copy the code

handleRelaunchActivityInner

    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        ...
        // Note that the third argument is true
        handleDestroyActivity(r.token, false, configChanges, true, reason); . handleLaunchActivity(r, pendingActions, customIntent);Copy the code

The handleDestroyActivity is called internally, and the third argument, getNonConfigInstance = true, will be discussed later in the handleLaunchActivity method

    @Override
    publicvoid handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance, reason); .Copy the code

Now look at the performDestroyActivity method which is the core implementation of the Activity destruction call.

    /** Core implementation of activity destroy call. */
    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        if(r ! =null) {
            activityClass = r.activity.getClass();
            r.activity.mConfigChangeFlags |= configChanges;
            if (finishing) {
                r.activity.mFinished = true;
            }

            performPauseActivityIfNeeded(r, "destroy");

            if(! r.stopped) { callActivityOnStop(r,false /* saveState */."destroy");
            }
            if (getNonConfigInstance) {
                try {
                	// Here comes the big point
                    r.lastNonConfigurationInstances
                            = r.activity.retainNonConfigurationInstances();
                } catch (Exception e) {
                    if(! mInstrumentation.onException(r.activity, e)) {throw new RuntimeException(
                                "Unable to retain activity "
                                + r.intent.getComponent().toShortString()
                                + ":"+ e.toString(), e); }}}Copy the code

Key in performDestroyActivity, r.l astNonConfigurationInstances = state Richard armitage ctivity. RetainNonConfigurationInstances (); Call the Activity object retainNonConfigurationInstances () and returns the value assigned to the ActivityClientRecord types of r objects LastNonConfigurationInstances properties.

Look again at Activity# retainNonConfigurationInstances did what:

    NonConfigurationInstances retainNonConfigurationInstances() {
    	/ / the key
        Object activity = onRetainNonConfigurationInstance();
        HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        // We're already stopped but we've been asked to retain.
        // Our fragments are taken care of but we need to mark the loaders for retention.
        // In order to do this correctly we need to restart the loaders first before
        // handing them off to the next activity.
        mFragments.doLoaderStart();
        mFragments.doLoaderStop(true);
        ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

        if (activity == null && children == null && fragments == null && loaders == null
                && mVoiceInteractor == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        if(mVoiceInteractor ! =null) {
            mVoiceInteractor.retainInstance();
            nci.voiceInteractor = mVoiceInteractor;
        }
        return nci;
    }
Copy the code

RetainNonConfigurationInstances calls onRetainNonConfigurationInstance (). Here, I know the onRetainNonConfigurationInstance () is how to call.

To the handleRelaunchActivityInner last call handleLaunchActivity back, Those familiar with the Activity launch process should know that handleLaunchActivity is an important step in starting an Activity

    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        ...
        // Note that the third argument is true
        handleDestroyActivity(r.token, false, configChanges, true, reason); . handleLaunchActivity(r, pendingActions, customIntent); }Copy the code

handleLaunchActivity

    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
         final Activity a = performLaunchActivity(r, customIntent);
    }
Copy the code

PerformLaunchActivity Launches the core implementation of an Activity

    /** Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if(r.state ! =null) { r.state.setClassLoader(cl); }}catch (Exception e) {
            if(! mInstrumentation.onException(activity, e)) {throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ":" + e.toString(), e);
            }
        }
        activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
Copy the code

Created the Activity instance and invoke the method of the Activity of the attach, note the attach method has a parameter was introduced into the r.l astNonConfigurationInstances, did very well, Just in performDestroyActivity, r.l astNonConfigurationInstances = state Richard armitage ctivity. RetainNonConfigurationInstances (); Call the Activity object retainNonConfigurationInstances () and returns the value assigned to the ActivityClientRecord types of r objects LastNonConfigurationInstances properties. It’s already strung together.

    @UnsupportedAppUsage
    finalvoid attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) { attachBaseContext(context); . mLastNonConfigurationInstances = lastNonConfigurationInstances; .Copy the code

In the attach the previously saved lastNonConfigurationInstances object assignment into new mLastNonConfigurationInstances Activity instance objects.

So just to review again how do I get the ViewModel

    val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)

	// The constructor of ViewModelProvider
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
Copy the code
	// 
    @NonNull
    @Override
    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.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); }}}Copy the code

The ensureViewModelStore() method is called to retrieve the ViewModelStore, EnsureViewModelStore () in its internal will call getLastNonConfigurationInstance () to obtain ViewModelStore object cache, if it returns, If no, create a new ViewModelStore instance.

GetLastNonConfigurationInstance:

    @Nullable
    public Object getLastNonConfigurationInstance() {
        returnmLastNonConfigurationInstances ! =null
                ? mLastNonConfigurationInstances.activity : null;
    }
Copy the code

GetLastNonConfigurationInstance internal return is just Activity# attach mLastNonConfigurationInstances object assignment.

How to save and restore the ViewModel to this problem solved!