Recently, I have been using Google to develop the Android-Architecture framework, and I feel quite comfortable with it. I especially like Room. Then when using ViewModel found an interesting phenomenon, the ViewModel not as onDestory destruction and rebuilding, searched a lot of answers, all say to fragments. SetRetainInstance (Boolean}, But the underlying reasons are murky (since the current version of Google has completely removed the implementation of Holderfragments and static variables). Let’s get to the bottom of it and figure out why.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewModelProvider provider = ViewModelProviders.of(this);
        mViewModel = provider.get(SimpleViewModel.class);
    }
Copy the code

The code above makes it very easy to create the ViewModel you need, such as SimpleViewModel here. We can tell if a Java object has changed by simply using hashCode. Of course, if the target object overwrites hashCode, we can tell by system.identityHashCode.

    object.hashCode();
    System.identityHashCode(object);
Copy the code

So, let’s do a simple transformation of the code

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "activity: " + this.hashCode());
        ViewModelProvider provider = ViewModelProviders.of(this);
        Log.d(TAG, "provider: " + provider.hashCode());
        mViewModel = provider.get(SimpleViewModel.class);
        Log.d(TAG, "mViewModel: " + mViewModel.hashCode());
    }
Copy the code

Using the code above we can easily see if these instances have changed. With the above code in place, rotate the screen and actually observe the logs printed before and after the rotation (be careful not to configure the rotation not to rebuild the Activity). Naturally, we found that the Activity was rebuilt, and the provider was rebuilt, but amazingly, the mViewModel was not rebuilt. Adding logs to onDestroy finds that the lifecycle is completely normal. In the past, I wrote an Activity source code analysis for an Activity that explained in detail where the Activity was created and why the Activity instance could be recycled after onDestroy. So with that in mind, let’s guess why mViewModel hasn’t been recreated? The mViewModel must be held by an object with a longer Activity life than the previous destroyed Activity.

  • What object has a longer Activity life cycle?
  • Static variables?
  • The Application?

The Activity of onRetainNonConfigurationInstance

The Activity can have a copy method is called: onRetainNonConfigurationInstance

    /**
     * Called by the system, as part of destroying an
     * activity due to a configuration change, when it is known that a new
     * instance will immediately be created for the new configuration.  You
     * can return any object you like here, including the activity instance
     * itself, which can later be retrieved by calling
     * {@link #getLastNonConfigurationInstance()} in the new activity
     * instance.
     *
     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
     * or later, consider instead using a {@link Fragment} with
     * {@linkFragment#setRetainInstance(boolean) * Fragment.setRetainInstance(boolean}.</em> * * <p>This function is called purely as  an optimization, and you must * not rely on it being called. When it is called, a number of guarantees * will be made to help optimize configuration switching: * <ul> * <li> The function will be called between {@link #onStop} and
     * {@link #onDestroy}.
     * <li> A new instance of the activity will <em>always</em> be immediately
     * created after this one's {@link #onDestroy()} is called.  In particular,
     * <em>no</em> messages will be dispatched during this time (when the returned
     * object does not have an activity to be associated with).
     * <li> The object you return here will <em>always</em> be available from
     * the {@link#getLastNonConfigurationInstance()} method of the following * activity instance as described there. * </ul> * * <p>These  guarantees are designed so that an activity can use this API * to propagate extensive state from the old to new activity instance, from * loaded bitmaps, to network connections, to evenly actively running * threads. Note that you should <em>not</em> propagate any data that * may change based on the configuration, including any data loaded from * resources such as strings, layouts, or drawables. * * <p>The guarantee of no message handling during the switch to the next * activity simplifies use with active objects. For example if your retained * state is an {@link android.os.AsyncTask} you are guaranteed that its
     * call back functions (like {@link android.os.AsyncTask#onPostExecute}) will
     * not be called from the call here until you execute the next instance's
     * {@link #onCreate(Bundle)}.  (Note however that there is of course no such
     * guarantee for {@link android.os.AsyncTask#doInBackground} since that is
     * running in a separate thread.)
     *
     * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API
     * {@link Fragment#setRetainInstance(boolean)} instead; this is also
     * available on older platforms through the Android support libraries.
     *
     * @return any Object holding the desired state to propagate to the
     *         next activity instance
     */
    public Object onRetainNonConfigurationInstance(a) {
        return null;
    }
Copy the code

If an object is returned by this method, it will be saved. Fragment calls setRetainInstance(Boolean), etc. Well, how do you get the object if you just save it? It’s the following method.

    /**
     * Retrieve the non-configuration instance data that was previously
     * returned by {@link #onRetainNonConfigurationInstance()}.  This will
     * be available from the initial {@link #onCreate} and
     * {@link #onStart} calls to the new instance, allowing you to extract
     * any useful dynamic state from the previous instance.
     *
     * <p>Note that the data you retrieve here should <em>only</em> be used
     * as an optimization for handling configuration changes.  You should always
     * be able to handle getting a null pointer back, and an activity must
     * still be able to restore itself to its previous state (through the
     * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
     * function returns null.
     *
     * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API
     * {@link Fragment#setRetainInstance(boolean)} instead; this is also
     * available on older platforms through the Android support libraries.
     *
     * @return the object previously returned by {@link #onRetainNonConfigurationInstance()}
     */
    @Nullable
    public Object getLastNonConfigurationInstance(a) {
        returnmLastNonConfigurationInstances ! =null
                ? mLastNonConfigurationInstances.activity : null;
    }
Copy the code

A lot of hoo-hoo-hoo-hoo. In short, this method can be used to retrieve previously saved instances. Also note that this instance can only be retrieved between onCreate onStart, onResume or beyond that. You can write a simple Activity copy of the above method to try out the effect. Of course if you inherit an appactivity instead of an Activity, you will find that this method cannot compatactivity because the method is marked as final by FragmentActivity preventing you from copying it.

    /**
     * Retain all appropriate fragment state.  You can NOT
     * override this yourself!  Use {@link #onRetainCustomNonConfigurationInstance()}
     * if you want to retain your own state.
     */
    @Override
    public final Object onRetainNonConfigurationInstance(a) {
        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }
Copy the code

In order to ensure that the ViewModel function works properly, Google forbids you to manually copy this method, because returning a new object after copying yourself would break the ViewModel’s ability to save instances, but Google also gives us a method for curving the country. Autotype onRetainCustomNonConfigurationInstance, of course, and the matching, take calls getLastCustomNonConfigurationInstance instance when they need. I want you to try it out for yourself, and see if you can rotate the screen, and see if you can get the same object that you saved on onCreate, and still have the same instance. And then this is save, so how does the ViewModel actually recover? In the onCreate method of the FragmentActivity class.

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

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if(nc ! = null && nc.viewModelStore ! = null && mViewModelStore == null) { mViewModelStore = nc.viewModelStore; }... }Copy the code

Google through getLastNonConfigurationInstance get here before save ViewModelStore, indirect ViewModel instance recovery, and without reconstruction. Also notice the FragmentManagerNonConfig fragments inside; Yet? This is what keeps the Fragment from rebuilding. See here more detailed implementation behind if you don’t care about, is actually about the same, this is very accurate (other than you search “fragments. SetRetainInstance (Boolean} more accurately).

Warren onRetainNonConfigurationInstance behind what happened?

It is already clear from the API level why the ViewModel can be restored. The ViewModel is not destroyed because the ViewModelStore, ViewModelStore didn’t be destroyed because of onRetainNonConfigurationInstance, the ViewModelStore after returning from onRetainNonConfigurationInstance, run where go to, What exactly is holding it so that it can be returned to the Activity the next time it is rebuilt? How do I find it? Let’s look at the onRetainNonConfigurationInstance callback when release the method of the main thread stack.

	  at com.aesean.SimpleActivity.onRetainCustomNonConfigurationInstance(LoginActivity.java:191)
	  at androidx.fragment.app.FragmentActivity.onRetainNonConfigurationInstance(FragmentActivity.java:569)
	  at android.app.Activity.retainNonConfigurationInstances(Activity.java:2423)
	  at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4469)
	  at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4515)
	  at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:4799)
	  at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4732)
	  at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:69)
	  at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
	  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
	  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
	  at android.os.Handler.dispatchMessage(Handler.java:106)
	  at android.os.Looper.loop(Looper.java:193)
	  at android.app.ActivityThread.main(ActivityThread.java:6718)
	  at java.lang.reflect.Method.invoke(Method.java:-1)
	  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
	  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Copy the code

Here we see clearly the method callback is ActivityThread performDestroyActivity invoked, we see ActivityThread (there are, in this kind of source in AndroidSdk To find ActivityThread, double-click Shift and type ActivityThread. If you cannot find ActivityThread, open the source code for the Activity. Ctrl+F to find ActivityThread.

    /** Core implementation of activity destroy call. */
    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); .if (getNonConfigInstance) {
                try {
                    r.lastNonConfigurationInstances
                            = r.activity.retainNonConfigurationInstances();
                } catch(Exception e) { ... mInstrumentation.callActivityOnDestroy(r.activity); . . mActivities.remove(token); .return r;
    }
Copy the code

I have pasted the main code, can be seen clearly in the Activity onDestroy is called before the callback retainNonConfigurationInstances, This time we returned to the instance of the object is assigned to the r.l astNonConfigurationInstances, r is ActivityClientRecord, he directly hold the Activity and other related Activity almost all of the information. We know that R holds the ViewModel indirectly, so as long as R can pass it back to the new Activity the next time it’s created, we can get the same ViewModel instance in onCreate. Looking at the method stack above, -> performDestroyActivity -> handleDestroyActivity -> handleRelaunchActivityInner -> handleRelaunchActivity PerformDestroyActivity executes and returns one layer down. We see handleRelaunchActivityInner in detail

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

Here the handleRelaunchActivityInner r.t oken to the handleDestroyActivity went on to the performDestroyActivity

    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); . }Copy the code

ActivityClientRecord r = mActivities.get(token); This line of r and r in handleRelaunchActivityInner is actually the same. So handleLaunchActivity r here is not new, but has just saved our lastNonConfigurationInstances instance of r, and the r and the new Activity.

    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {...finalActivity a = performLaunchActivity(r, customIntent); . }Copy the code
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {... 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);

                if(customIntent ! =null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null; . }Copy the code

Here the r.l astNonConfigurationInstances passed on to the activity of the attach method, at the same time r cleared lastNonConfigurationInstances references.

    final void 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) {... mLastNonConfigurationInstances = lastNonConfigurationInstances; . }Copy the code

MLastNonConfigurationInstances actually got lastNonConfigurationInstances here, As the only way for the new Activity mLastNonConfigurationInstances hold lastNonConfigurationInstances. You can only get the ViewModelStore on onCreate and onStart.

    final void performResume(boolean followedByPause, String reason) {... mLastNonConfigurationInstances =null; . }Copy the code

In the Activity of performResume mLastNonConfigurationInstances reset to null.

conclusion

Well here is a thorough source level layer by layer to get to the bottom of the matter, thoroughly explain this thing. To summarize, the reason the ViewModel is not rebuilt when the screen is rotated is because the Activity RELAUNCH occurs when the screen is rotated, and the ActivityClientRecord is reused during the RELAUNCH. The ViewModelStore is also stored in the ActivityClientRecord. The ViewModelStore that the ActivityClientRecord uses when the new Activity is launched is passed to the new Activity. The FragmentActivity then takes the previously saved ViewModelStore instance, and the Activity rebuilds, but the ViewModel is not rebuilt. Going back to the beginning of the article,

  • What object has a longer Activity life cycle? ActivityClientRecord at RELAUNCH time
  • Static variables? Obviously, we don’t use static variables to save the ViewModel
  • The Application? Obviously saving the ViewModel doesn’t use the Application either

In addition, the many articles to tell you why you found the ViewModel instance not rebuild because fragments. SetRetainInstance (Boolean}? This is mainly because early ViewModel implementations were still implemented through HolderFragments and static variables, but holderfragments have been completely removed. Obviously implementation than HolderFragment much better now, did not use any static variables in this version, use the Api1 there have already been onRetainCustomNonConfigurationInstance perfect solve the problem of instance holds. If you have any questions, please leave a comment.