The Activity state is saved and restored


Long time no update…… How can business be so volume…… Orz.

Before I didn’t quite understand when the view can save the state, when the state can be restored, I still have to think about the source code, although the work is more and more far from Android, but after all, the old line, still need to spend energy to think about.


The timing of an Activity’s state saving varies from version to version. Prior to Android 3, To save the Activity state, onSaveInstanceState (ActivityThread#performPauseActivity) is called before onPause. Before Android 9, Save the Activity state before onStop. After Android 9, save the Activity state after onStop (ActivityThread#callActivityOnStop).

And through the previous analysis:

The Android Activity startup process

Android UI Workflow 1

Android UI Workflow 2

As you can see, when AMS creates an Activity, a Binder transaction is called to the client process to execute the LaunchActivityItem#execute, which includes the creation of an ActivityClientRecord.

    public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) {
        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
        ActivityClientRecord r = newActivityClientRecord(... , mState,...) ; client.handleLaunchActivity(r, pendingActions,null /* customIntent */);
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }
Copy the code

An ActivityClientRecord represents an Activity instance record for the user process. The mState field of the Bundle type represents the state of the Activity.

Back to see ActivityThread# callActivityOnStop, eventually logic is calling to ActivityThread# callActivityOnSaveInstanceState method:

    private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
        // create a new Bundle object for ActivityClientRecord#state.
        r.state = new Bundle();
        r.state.setAllowFds(false);// The serialization layer has two sets of logic to support file descriptors, which is set to false because the bundle does not need to store FDS
        if (r.isPersistable()) { // If it is a built-in application
            r.persistentState = new PersistableBundle();
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
                    r.persistentState);
        } else {
            // For normal apps, save the state with the following callmInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state); }}Copy the code

-> Instrumentation#callActivityOnSaveInstanceState

-> activity.performSaveInstanceState

    final void performSaveInstanceState(@NonNull Bundle outState) {
        dispatchActivityPreSaveInstanceState(outState);/ / can register ActivityLifecycleCallbacks to Application for the hook callback.
        onSaveInstanceState(outState);// Execute the onSaveInstanceState method of the Activity instance.
        saveManagedDialogs(outState);// Save the Dialog state associated with the current Activity.
        mActivityTransitionState.saveState(outState);// Save the state of the Activity transition animation
        storeHasCurrentPermissionRequest(outState);// Save whether the current Activity is applying for permission
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ":" + outState);
        dispatchActivityPostSaveInstanceState(outState);// Similar to the pre above, it also issues a hook callback.
    }
Copy the code

So the core is the Activity’s onSaveInstanceState method, and we know that we can override this method to implement some additional state-saving logic, even if we don’t override the Activity instance’s onSaveInstanceState method, There is also some default logic for activities. The default implementation is:

		protected void onSaveInstanceState(@NonNull Bundle outState) {
        // Core holds view state through the Window's saveHierarchyState.
        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
        outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
        // Internally stores all Fragment states held by the Activity using FragmentManager
        Parcelable p = mFragments.saveAllState();
        if(p ! =null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
        // Automatically populate the service-related logic
        if (mAutoFillResetNeeded) {
            outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
            getAutofillManager().onSaveInstanceState(outState);
        }
        dispatchActivitySaveInstanceState(outState);
    }
Copy the code

View state save and restore:

View state saving:

The Window instance is PhoneWindow of course:

    public Bundle saveHierarchyState(a) {
        Bundle outState = new Bundle();
        // If the Window has no content, there is nothing to save
        // mContentParent is the FrameLayout of the Activity structure R.i c Tent
        if (mContentParent == null) {
            return outState;
        }
        // Create a loose array structure hash table
        SparseArray<Parcelable> states = new SparseArray<Parcelable>();
        // See below:
        mContentParent.saveHierarchyState(states);
        // Put the state of the view tree into the Bundle passed in by the Activity
        outState.putSparseParcelableArray(VIEWS_TAG, states);
        // Save the id of the View that is currently getting focus
        final View focusedView = mContentParent.findFocus();
        if(focusedView ! =null&& focusedView.getId() ! = View.NO_ID) { outState.putInt(FOCUSED_ID_TAG, focusedView.getId()); }// ...
        return outState;
    }
Copy the code

MContentParent. SaveHierarchyState (states), the implementation is the View:

/ / the dispatchSaveInstanceState implementation is as follows:
    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        // As long as the view has an ID and does not turn off the state saving function, it will be saved
        if(mID ! = NO_ID && (mViewFlags & SAVE_DISABLED_MASK) ==0) {
            mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
            // Call back to View's onSaveInstanceState implementation
            Java returns an empty Parcelable object (and some Autofill related logic).
            Parcelable state = onSaveInstanceState();
            if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
                throw new IllegalStateException(
                        "Derived class did not call super.onSaveInstanceState()");
            }
            if(state ! =null) {
                // In the sparse array created above, the key is the View ID.container.put(mID, state); }}}Copy the code

A View saves its state, while a VierGroup must iterate over its sub-view to save its state:

    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        super.dispatchSaveInstanceState(container);
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            View c = children[i];
            if((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) ! = PARENT_SAVE_DISABLED) { c.dispatchSaveInstanceState(container); }}}Copy the code

The state of the view tree is saved, and we can take a look at the state of a Switch component like Switch, because Switch inherits from CompoundButton and does not implement its own onSaveInstanceState method in CompoundButton:

    public Parcelable onSaveInstanceState(a) {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.checked = isChecked();// Save the checked state of the button.
        return ss;
    }
Copy the code

View state restoration:

The state of the view is saved, and the next step is to consider restoring it at the appropriate time, again using the Switch control as an example:

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        // Restore the selected state.
        setChecked(ss.checked);
        requestLayout();
    }
Copy the code

When the Activity starts, it automatically calls back to the relevant logic:

-> Activity#handleStartActivity

-> Instrumentation#callActivityOnRestoreInstanceState

-> activity.performRestoreInstanceState

    final void performRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        / / callback onRestoreInstanceState
        onRestoreInstanceState(savedInstanceState);
        // Restore the Dialog state
        restoreManagedDialogs(savedInstanceState);
    }
Copy the code

The view state is saved from PhoneWindow, and the same is true for restoration. In the default implementation of onRestoreInstanceState, the Bundle object of the Window state is retrieved. Then mWindow. RestoreHierarchyState recovery, in fact, and serialization process is very similar, the state of the reverse recovery process is preserved, the state of the view tree recovery key code:

mContentParent.restoreHierarchyState(savedStates);
Copy the code

MContentParent is an instance of FrameLayout, which itself does not implement restoreHierarchyState, in the View:

-> View#restoreHierarchyState

-> View#dispatchRestoreInstanceState

    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
        if(mID ! = NO_ID) { Parcelable state = container.get(mID);// If there is the current View state
            if(state ! =null) {
                mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
                // If so, call onRestoreInstanceStateonRestoreInstanceState(state); }}}Copy the code

The implementation of onRestoreInstanceState in the child View is just like the implementation of the Switch above.

Fragment state save and restore:

Fragment state:

In the onSaveInstanceState section of the Activity, we see the fragment-related save logic:

-> mFragments.saveAllState()

-> mHost.mFragmentManager.saveAllState()

The FragmentManagerImpl implementation is as follows:

    Parcelable saveAllState(a) {
        // Force some pre-tasks, such as executing all committed transactions, to keep the state up to date.
        // Smell something bad
        forcePostponedTransactions();
        endAnimatingAwayFragments();
        execPendingActions();

        mStateSaved = true;
        mSavedNonConfig = null;
        // If there is no active Fragment, it will not be saved
        if (mActive == null || mActive.size() <= 0) {
            return null;
        }
        // First collect all active fragments.
        int N = mActive.size();
        FragmentState[] active = new FragmentState[N];
        boolean haveFragments = false;
        for (int i=0; i<N; i++) {
            Fragment f = mActive.valueAt(i);
            if(f ! =null) {
                if (f.mIndex < 0) {
                    throwException(new IllegalStateException(
                            "Failure saving state: active " + f
                            + " has cleared index: " + f.mIndex));
                }
                haveFragments = true;
                FragmentState fs = new FragmentState(f);
                active[i] = fs;
                
                if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
                    // Save the Fragment object's state
                    // There are two main things to do here. The first is to call the Fragment's own onSave method to save its state
                    // The second is to call the Fragment's mChildFragmentManager to store the state of the child Fragment recursively.
                    fs.mSavedFragmentState = saveFragmentBasicState(f);
                    if(f.mTarget ! =null) {
                        // Save the index of the Fragment's TargetFragment recorded in mActive.
                        if (f.mTarget.mIndex < 0) {
                            throwException(new IllegalStateException(
                                    "Failure saving state: " + f
                                    + " has target not in fragment manager: " + f.mTarget));
                        }
                        if (fs.mSavedFragmentState == null) {
                            fs.mSavedFragmentState = new Bundle();
                        }
                        putFragment(fs.mSavedFragmentState,
                                FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget);
                        if(f.mTargetRequestCode ! =0) { fs.mSavedFragmentState.putInt( FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, f.mTargetRequestCode); }}}else {
                    fs.mSavedFragmentState = f.mSavedFragmentState;
                }
                if (DEBUG) Log.v(TAG, "Saved state of " + f + ":"+ fs.mSavedFragmentState); }}if(! haveFragments) {if (DEBUG) Log.v(TAG, "saveAllState: no fragments!");
            return null;
        }
        
        int[] added = null;
        BackStackState[] backStack = null;
        
        // Build list of currently added fragments.
        N = mAdded.size();
        if (N > 0) {
            added = new int[N];
            for (int i=0; i<N; i++) {
                added[i] = mAdded.get(i).mIndex;
                if (added[i] < 0) {
                    throwException(new IllegalStateException(
                            "Failure saving state: active " + mAdded.get(i)
                            + " has cleared index: " + added[i]));
                }
                if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
                        + ":"+ mAdded.get(i)); }}// Save the state of the Fragment rollback
        if(mBackStack ! =null) {
            N = mBackStack.size();
            if (N > 0) {
                backStack = new BackStackState[N];
                for (int i=0; i<N; i++) {
                    backStack[i] = new BackStackState(this, mBackStack.get(i));
                    if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i
                            + ":"+ mBackStack.get(i)); }}}// Create a Fragment state object and write all the collected state data to the FMS, which is then handed to the Activity to save.
        FragmentManagerState fms = new FragmentManagerState();
        fms.mActive = active;
        fms.mAdded = added;
        fms.mBackStack = backStack;
        fms.mNextFragmentIndex = mNextFragmentIndex;
        if(mPrimaryNav ! =null) {
            fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
        }
        saveNonConfig();
        return fms;
    }
Copy the code

Fragment state management can be very complex, especially when it comes to active Fragment state management.

In addition to call FM in fragments in the Activity state of preservation, FragmentManager also provides public methods: saveFragmentInstanceState (fragments), can be preserved for a single Fragment.

And when changing the Fragment state in the FragmentManager’s moveToState, when the Fragment state falls back before ACTIVITY_CREATE, This is the initialization state (the Fragment will be in this state before performing onDestroy) and the invalid state.

Fragment state restoration:

According to Google’s coding practices, state recovery must also be in the FragmentManagerImpl, and the API is called restoreAllState, which is corresponding to the above three state stores.

The Fragment entry for the Activity is in the Acitvity onCreate:

        if(savedInstanceState ! =null) {
            // ...
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            // Call the restoreAllState method of the Fragment.mFragments.restoreAllState(p, mLastNonConfigurationInstances ! =null
                    ? mLastNonConfigurationInstances.fragments : null);
        }
        mFragments.dispatchCreate();
Copy the code

The restoreAllState method is divided into the following steps:

  1. Restore the Fragment instance state affected by Activity rebuilding.
  2. Create and restore all Fragment instances in mActive.
  3. Update the mTarget index of the Fragment.
  4. Add the stack of fragments above to the mAdded container in FragmentManager.
  5. Restore the Fragment’s return stack state if necessary.

For external use active saveFragmentInstanceState state method, the corresponding fitness method is the setInitialSavedState fragments, this method is actually the state object assigned to fragments, The FragmentManager will then actively pass the Fragment’s existing state object to the corresponding method, such as onCreateView, when executing the Fragment’s declaration cycle.

CREATED: onCreateView, onViewCreated: onCreateView, onViewCreated: onCreateView, onViewCreated: onCreateView, onViewCreated: onCreateView, onViewCreated OnActivityCreated to the Fragment. And by FragmentLifecycleCallbacks exposed to outside. Call the Fragment’s restoreViewState to restore the view state, starting with the Fragment’s mView field, which is the object returned by onCreateView. After the Fragment has completed the creation of the Fragment view through onCreateView, FM will assign it to the mView field of the Fragment.

Dialog state save and restore:

Save Dialog state:

The source code for this part of the Activity is submitted in 2009. The relevant logic is actually to call all instances of the current Activity, save and restore the Dialog. DialogFragment is also an instance of a Fragment. It is managed by FragmentManager, so state preservation and recovery are not very different from those of a Fragment. DialogFragment implements a custom onSaveInstanceState method) :

    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if(mDialog ! =null) {
            // Call the onSaveInstanceState method of the mDialog associated with the current DialogFragment
            Bundle dialogState = mDialog.onSaveInstanceState();
            if(dialogState ! =null) { outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState); }}if(mStyle ! = STYLE_NORMAL) { outState.putInt(SAVED_STYLE, mStyle); }if(mTheme ! =0) {
            outState.putInt(SAVED_THEME, mTheme);
        }
        if(! mCancelable) { outState.putBoolean(SAVED_CANCELABLE, mCancelable); }// If the current DialogFragment is displaying a Dialog, record this bit
        if(! mShowsDialog) { outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); }if(mBackStackId ! = -1) { outState.putInt(SAVED_BACK_STACK_ID, mBackStackId); }}Copy the code

The implementation in Dialog is relatively simple compared to other components, which is whether the Dialog is showing token bit saving and view state saving:

    public @NonNull Bundle onSaveInstanceState(a) {
        Bundle bundle = new Bundle();
        // Saves whether the current Dialog is in display state
        bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing);
        if (mCreated) {
            // Save the PhoneWindow view state associated with Dialog
            bundle.putBundle(DIALOG_HIERARCHY_TAG, mWindow.saveHierarchyState());
        }
        return bundle;
    }
Copy the code

The status is restored as follows:

    public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG);
        if (dialogHierarchyState == null) {
            // dialog has never been shown, or onCreated, nothing to restore.
            return;
        }
        // If the Dialog has not executed onCreate before, execute onCreate.
        dispatchOnCreate(savedInstanceState);
        // Restore the state of the view tree associated with Dialog.
        mWindow.restoreHierarchyState(dialogHierarchyState);
        // If the Dialog was displayed before, show it again
        if(savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) { show(); }}Copy the code