An overview of the

We know that activities provide onSaveInstanceState and onRestoreInstanceState callbacks for state preservation and restoration, as well as FragmentActivity and Fragment callbacks. The FragmentActivity saves the Fragment with the FragmentManagerImpl notification when appropriate, and then traces the notification process from the source code.

The source code to explore

The source based on ‘androidx. Fragments: fragments: 1.1.0’

State of preservation

FragmentActivity overwrites the onSaveInstanceState method: [FragmentActivity.java]

protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    / /...
    // Call the saveAllState method of FragmentController to get Parcelable
    Parcelable p = mFragments.saveAllState();
    if(p ! =null) {
        / / to save Parcelable into outState FRAGMENTS_TAG value is "android: support: fragments could"
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    / /...
}
Copy the code

In outState, FRAGMENTS_TAG is used as the key to store data.

The FragmentController saveAllState method is invoked in the FragmentController saveAllState method: [FragmentManagerImp.java]

Parcelable saveAllState(a) {
    // Make sure all pending operations have now been executed to get
    // our state update-to-date.
    forcePostponedTransactions();
    endAnimatingAwayFragments();
    execPendingActions();

    // Save the flag state to true
    mStateSaved = true;

    if (mActive.isEmpty()) {
        return null;
    }

    // First collect all active fragments.
    int size = mActive.size();
    ArrayList<FragmentState> active = new ArrayList<>(size);
    boolean haveFragments = false;
    for (Fragment f : mActive.values()) {
        if(f ! =null) {
            if(f.mFragmentManager ! =this) {
                throwException(new IllegalStateException(
                        "Failure saving state: active " + f
                                + " was removed from the FragmentManager"));
            }

            haveFragments = true;

            // Create FragmentState to hold member variable values in the Fragment
            FragmentState fs = new FragmentState(f);
            active.add(fs);

            // fs.mSavedFragmentState Is null by default
            if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
                // Send a notification fragment to save data
                fs.mSavedFragmentState = saveFragmentBasicState(f);

                // If there is a setTargetFragment, save TargetFragment data
                if(f.mTargetWho ! =null) {
                    Fragment target = mActive.get(f.mTargetWho);
                    if (target == null) {
                        throwException(new IllegalStateException(
                                "Failure saving state: " + f
                                        + " has target not in fragment manager: "
                                        + f.mTargetWho));
                    }
                    if (fs.mSavedFragmentState == null) {
                        fs.mSavedFragmentState = new Bundle();
                    }
                    putFragment(fs.mSavedFragmentState,
                            FragmentManagerImpl.TARGET_STATE_TAG, target);
                    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;
    }

    ArrayList<String> added = null;
    BackStackState[] backStack = null;

    // Build list of currently added fragments.
    // Save the Fragment ID in the mAdded collection
    size = mAdded.size();
    if (size > 0) {
        added = new ArrayList<>(size);
        for (Fragment f : mAdded) {
            // f.who is the unique UUID generated when the Fragment is instantiated
            added.add(f.mWho);
            if(f.mFragmentManager ! =this) {
                throwException(new IllegalStateException(
                        "Failure saving state: active " + f
                                + " was removed from the FragmentManager"));
            }
            if (DEBUG) {
                Log.v(TAG, "saveAllState: adding fragment (" + f.mWho
                        + ")."+ f); }}}// Now save back stack.
    // If addToBackStack is present, the data related to the rollback is saved
    if(mBackStack ! =null) {
        size = mBackStack.size();
        if (size > 0) {
            backStack = new BackStackState[size];
            for (int i = 0; i < size; i++) {
                // Create BackStackState to save data for BackStackRecord
                backStack[i] = new BackStackState(mBackStack.get(i));
                if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i
                        + ":"+ mBackStack.get(i)); }}}// Create FragmentManagerState to hold the FragmentManagerImpl data as a whole
    FragmentManagerState fms = new FragmentManagerState();
    fms.mActive = active;
    fms.mAdded = added;
    fms.mBackStack = backStack;
    if(mPrimaryNav ! =null) {
        fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
    }
    fms.mNextFragmentIndex = mNextFragmentIndex;
    return fms;
}
Copy the code

The FragmentState collection is created to hold the data for each Fragment, the String collection holds the unique ID for each Fragment, and the BackStackState array holds the data for each BackStackRecord. Finally create FragmentManagerState to hold all of the above data and return it to the Bundle.

Next look at the saveFragmentBasicState method, which does more detailed data saving by returning the mSavedFragmentState member assigned to the Bundle by FragmentState: [FragmentManagerImp.java]

Bundle saveFragmentBasicState(Fragment f) {
    Bundle result = null;

    if (mStateBundle == null) {
        mStateBundle = new Bundle();
    }
    // Trigger the Fragment onSaveInstanceState callback method; Notify the sub-fragment of the Fragment to save the state
    f.performSaveInstanceState(mStateBundle);
    // Lifecycle architecture components callback notifications
    dispatchOnFragmentSaveInstanceState(f, mStateBundle, false);
    if(! mStateBundle.isEmpty()) { result = mStateBundle; mStateBundle =null;
    }

    // If the fragment has a View set, save the view tree
    if(f.mView ! =null) {
        saveFragmentViewState(f);
    }
    if(f.mSavedViewState ! =null) {
        if (result == null) {
            result = new Bundle();
        }
        // Add view tree data
        result.putSparseParcelableArray(
                FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
    }
    if(! f.mUserVisibleHint) {if (result == null) {
            result = new Bundle();
        }
        // Only add this if it's not the default value
        // Save mUserVisibleHint when not visible
        result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint);
    }

    return result;
}
Copy the code

In this method, the onSaveInstanceState callback of the Fragment is triggered by the performSaveInstanceState method. The Fragment subclass can override this method to save data. If the Fragment has a set view, the view tree is also saved through the saveHierarchyState method of the Fragment’s mInnerView members.

After this series of saves, the data is consolidated and added to the Bundle, stored by the State member of ActivityClientRecord, and the FragmentManagerImpl’s mStateSaved is marked true.

back

Enter the FragmentActivity onCreate method: [fragmentActivity.java]

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

    if(savedInstanceState ! =null) {
        // If savedInstanceState is not empty, retrieve the data corresponding to FRAGMENTS_TAG
        Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
        // Pass the data to FragmentManagerImpl for state recovery
        mFragments.restoreSaveState(p);

        // Check if there are any pending onActivityResult calls to descendent Fragments.
        if (savedInstanceState.containsKey(NEXT_CANDIDATE_REQUEST_INDEX_TAG)) {
            mNextCandidateRequestIndex =
                    savedInstanceState.getInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG);
            int[] requestCodes = savedInstanceState.getIntArray(ALLOCATED_REQUEST_INDICIES_TAG);
            String[] fragmentWhos = savedInstanceState.getStringArray(REQUEST_FRAGMENT_WHO_TAG);
            if (requestCodes == null || fragmentWhos == null|| requestCodes.length ! = fragmentWhos.length) { Log.w(TAG,"Invalid requestCode mapping in savedInstanceState.");
            } else {
                mPendingFragmentActivityResults = new SparseArrayCompat<>(requestCodes.length);
                for (int i = 0; i < requestCodes.length; i++) { mPendingFragmentActivityResults.put(requestCodes[i], fragmentWhos[i]); }}}}if (mPendingFragmentActivityResults == null) {
        mPendingFragmentActivityResults = new SparseArrayCompat<>();
        mNextCandidateRequestIndex = 0;
    }

    super.onCreate(savedInstanceState);

    mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    mFragments.dispatchCreate();
}
Copy the code

In onCreate, if savedInstanceState is not empty, retrieve the data corresponding to FRAGMENTS_TAG and use the restoreSaveState method of FragmentManagerImpl to restore the status.

Next look at the restoreSaveState method: [FragmentManagerImp.java]

void restoreSaveState(Parcelable state) {
    // If there is no saved state at all, then there's nothing else to do
    if (state == null) return;
    FragmentManagerState fms = (FragmentManagerState)state;
    if (fms.mActive == null) return;

    // First re-attach any non-config instances we are retaining back
    // to their saved state, so we don't try to instantiate them again.
    // Handle state recovery of fragments associated with setRetainInstance
    for (Fragment f : mNonConfig.getRetainedFragments()) {
        if (DEBUG) Log.v(TAG, "restoreSaveState: re-attaching retained " + f);
        FragmentState fs = null;
        for (FragmentState fragmentState : fms.mActive) {
            if (fragmentState.mWho.equals(f.mWho)) {
                fs = fragmentState;
                break; }}if (fs == null) {
            // If the corresponding RetainedFragment does not exist, remove the RetainedFragment
            if (DEBUG) {
                Log.v(TAG, "Discarding retained Fragment " + f
                        + " that was not found in the set of active Fragments " + fms.mActive);
            }
            // We need to ensure that onDestroy and any other clean up is done
            // so move the Fragment up to CREATED, then mark it as being removed, then
            // destroy it.
            moveToState(f, Fragment.CREATED, 0.0.false);
            f.mRemoving = true;
            moveToState(f, Fragment.INITIALIZING, 0.0.false);
            continue;
        }
        fs.mInstance = f;
        f.mSavedViewState = null;
        f.mBackStackNesting = 0;
        f.mInLayout = false;
        f.mAdded = false; f.mTargetWho = f.mTarget ! =null ? f.mTarget.mWho : null;
        f.mTarget = null;
        if(fs.mSavedFragmentState ! =null) {
            // Restore RetainedFragment datafs.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader()); f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG); f.mSavedFragmentState = fs.mSavedFragmentState; }}// Build the full list of active fragments, instantiating them from
    // their saved state.
    mActive.clear();
    for (FragmentState fs : fms.mActive) {
        if(fs ! =null) {
            // Instantiate the Fragment and initialize the Fragment with the data stored in FragmentState
            Fragment f = fs.instantiate(mHost.getContext().getClassLoader(),
                    getFragmentFactory());
            f.mFragmentManager = this;
            if (DEBUG) Log.v(TAG, "restoreSaveState: active (" + f.mWho + ")." + f);
            // Add fragment to mActive member
            mActive.put(f.mWho, f);
            // Now that the fragment is instantiated (or came from being
            // retained above), clear mInstance in case we end up re-restoring
            // from this FragmentState again.
            fs.mInstance = null; }}// Build the list of currently added fragments.
    mAdded.clear();
    if(fms.mAdded ! =null) {
        for (String who : fms.mAdded) {
            // Get the fragment you just created and saved
            Fragment f = mActive.get(who);
            if (f == null) {
                throwException(new IllegalStateException(
                        "No instantiated fragment for (" + who + ")"));
            }
            f.mAdded = true;
            if (DEBUG) Log.v(TAG, "restoreSaveState: added (" + who + ")." + f);
            if (mAdded.contains(f)) {
                throw new IllegalStateException("Already added " + f);
            }
            synchronized (mAdded) {
                // Add to the mAdded membermAdded.add(f); }}}// Build the back stack.
    // Back up data recovery
    if(fms.mBackStack ! =null) {
        mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length);
        for (int i=0; i<fms.mBackStack.length; i++) {
            BackStackRecord bse = fms.mBackStack[i].instantiate(this);
            if (DEBUG) {
                Log.v(TAG, "restoreAllState: back stack #" + i
                        + " (index " + bse.mIndex + ")." + bse);
                LogWriter logw = new LogWriter(TAG);
                PrintWriter pw = new PrintWriter(logw);
                bse.dump("", pw, false);
                pw.close();
            }
            mBackStack.add(bse);
            if (bse.mIndex >= 0) { setBackStackIndex(bse.mIndex, bse); }}}else {
        mBackStack = null;
    }

    if(fms.mPrimaryNavActiveWho ! =null) {
        mPrimaryNav = mActive.get(fms.mPrimaryNavActiveWho);
        dispatchParentPrimaryNavigationFragmentChanged(mPrimaryNav);
    }
    this.mNextFragmentIndex = fms.mNextFragmentIndex;
}
Copy the code

In this method, data stored in FragmentManagerState is used for state recovery. When FragmentState is used to restore Fragment data, MSavedFragmentState is assigned to the mSavedFragmentState member of the Fragment. MSavedFragmentState stores the custom stored data and view tree state in the Fragment

Later, as FragmentActivity schedules the Fragment to be displayed, the data saved by mSavedFragmentState can be restored at various stages of the Fragment lifecycle.

Enter the FragmentManagerImpl lifecycle state scheduling method moveToState: [FragmentManagerImp.java]

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
                 boolean keepActive) {
    / /...
    if (f.mState <= newState) {
        / /...
        switch (f.mState) {
            case Fragment.INITIALIZING:
                if (newState > Fragment.INITIALIZING) {
                    if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
                    if(f.mSavedFragmentState ! =null) {
                        f.mSavedFragmentState.setClassLoader(mHost.getContext()
                                .getClassLoader());
                        // Get the state of the saved view treef.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG); Fragment target = getFragment(f.mSavedFragmentState, FragmentManagerImpl.TARGET_STATE_TAG); f.mTargetWho = target ! =null ? target.mWho : null;
                        if(f.mTargetWho ! =null) {
                            f.mTargetRequestCode = f.mSavedFragmentState.getInt(
                                    FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
                        }
                        if(f.mSavedUserVisibleHint ! =null) {
                            f.mUserVisibleHint = f.mSavedUserVisibleHint;
                            f.mSavedUserVisibleHint = null;
                        } else {
                            // Get the saved visible state
                            f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
                                    FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
                        }
                        if(! f.mUserVisibleHint) { f.mDeferStart =true;
                            if(newState > Fragment.ACTIVITY_CREATED) { newState = Fragment.ACTIVITY_CREATED; }}}/ /...
                    
                    if (f.mParentFragment == null) {
                        mHost.onAttachFragment(f);
                    } else {
                        f.mParentFragment.onAttachFragment(f);
                    }
                    dispatchOnFragmentAttached(f, mHost.getContext(), false);

                    if(! f.mIsCreated) { dispatchOnFragmentPreCreated(f, f.mSavedFragmentState,false);
                        // Trigger the Fragment's onCreate callback and pass in the bundle
                        f.performCreate(f.mSavedFragmentState);
                        dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
                    } else {
                        // The status of the child Fragment sent is restoredf.restoreChildFragmentState(f.mSavedFragmentState); f.mState = Fragment.CREATED; }}// fall through
            case Fragment.CREATED:
                // We want to unconditionally run this anytime we do a moveToState that
                // moves the Fragment above INITIALIZING, including cases such as when
                // we move from CREATED => CREATED as part of the case fall through above.
                if (newState > Fragment.INITIALIZING) {
                    ensureInflatedFragmentView(f);
                }

                if (newState > Fragment.CREATED) {
                    if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
                    if(! f.mFromLayout) { ViewGroup container =null;
                        if(f.mContainerId ! =0) {
                            / /...
                        }
                        f.mContainer = container;
                        // Trigger the onCreateView callback of the Fragment and pass in the bundle
                        f.performCreateView(f.performGetLayoutInflater(
                                f.mSavedFragmentState), container, f.mSavedFragmentState);
                        if(f.mView ! =null) {
                            f.mInnerView = f.mView;
                            f.mView.setSaveFromParentEnabled(false);
                            if(container ! =null) {
                                container.addView(f.mView);
                            }
                            if (f.mHidden) {
                                f.mView.setVisibility(View.GONE);
                            }
                            // Trigger the Fragment's onViewCreated callback and pass in the bundle
                            f.onViewCreated(f.mView, f.mSavedFragmentState);
                            dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
                                    false);
                            // Only animate the view if it is visible. This is done after
                            // dispatchOnFragmentViewCreated in case visibility is changedf.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE) && f.mContainer ! =null;
                        } else {
                            f.mInnerView = null; }}// Trigger the Fragment's onActivityCreated callback and pass it to the bundle
                    f.performActivityCreated(f.mSavedFragmentState);
                    dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
                    if(f.mView ! =null) {
                        // Call mInnerView's restoreHierarchyState method to pass in mSavedViewState for view tree state restoration.
                        // Then call the onViewStateRestored method and pass in the bundle.
                        f.restoreViewState(f.mSavedFragmentState);
                    }
                    // mSavedFragmentState is set to null
                    f.mSavedFragmentState = null;
                }
                // fall through
            case Fragment.ACTIVITY_CREATED:
                if (newState > Fragment.ACTIVITY_CREATED) {
                    if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                    f.performStart();
                    dispatchOnFragmentStarted(f, false);
                }
                // fall through
            case Fragment.STARTED:
                if (newState > Fragment.STARTED) {
                    if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
                    f.performResume();
                    dispatchOnFragmentResumed(f, false);
                    f.mSavedFragmentState = null;
                    f.mSavedViewState = null; }}}else if (f.mState > newState) { / *... * / }
    / /...
}
Copy the code

As you can see from this method, the savedInstanceState input received in the Fragment’s lifecycle callback is the Fragment’s mSavedFragmentState, which is then assigned to null after dispatch.

commit & commitAllowingStateLoss

Here’s a look at two methods for committing a transaction: COMMIT and commitAllowingStateLoss. As the name suggests, one does not allow state loss on commit and the other does.

[BackStackRecord.java]

public int commit(a) {
    return commitInternal(false);
}

public int commitAllowingStateLoss(a) {
    return commitInternal(true);
}
Copy the code

Both call the same method commitInternal with different arguments, one passing false and the other passing true.

Continue with the commitInternal method: [BackStackRecord.java]

int commitInternal(boolean allowStateLoss) {
    / /...
    mManager.enqueueAction(this, allowStateLoss);
    return mIndex;
}
Copy the code

The allowStateLoss argument is passed directly to the method that adds the transaction queue.

[FragmentManagerImpl.java]

public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
    if(! allowStateLoss) {// Check first if state loss is not allowed
        checkStateLoss();
    }
    synchronized (this) {
        if (mDestroyed || mHost == null) {
            // If the FragmentActivity has destroyed or holds FragmentHostCallback empty, an exception will be thrown if state loss is not allowed.
            if (allowStateLoss) {
                // This FragmentManager isn't attached, so drop the entire transaction.
                return;
            }
            throw new IllegalStateException("Activity has been destroyed");
        }
        if (mPendingActions == null) {
            mPendingActions = newArrayList<>(); } mPendingActions.add(action); scheduleCommit(); }}Copy the code

Whether to allow state loss is to check the state and throw an exception before adding a transaction to the queue.

Enter the checkStateLoss method: [FragmentManagerImp.java]

private void checkStateLoss(a) {
    // Check the status to determine whether exceptions are thrown
    if (isStateSaved()) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState"); }}@Override
public boolean isStateSaved(a) {
    // See saveAllState() for the explanation of this. We do this for
    // all platform versions, to keep our behavior more consistent between
    // them.
    // mStateSaved will be set to true for onSaveInstanceState and mStopped will be set to true for onStop
    return mStateSaved || mStopped;
}
Copy the code

It is not allowed to add a Fragment transaction after executing the onSaveInstanceState callback method. The Fragment state is saved in the onSaveInstanceState phase. If you change the Fragment state in the FragmentManagerImpl, the Fragment state is not saved.

The onSaveInstanceState callback is triggered after onStop on Android P or later, and before onStop on Android P or later.

conclusion

FragmentActivity In the onSaveInstanceState callback method, call FragmentManagerImpl to inform its managed Fragment to save state. The onSaveInstanceState callback of the Fragment is triggered during saving. You can override this method to save custom data. If the Fragment has a view, it will also save the view tree.

In the onCreate callback method, determine if the savedInstanceState parameter has a value and inform FragmentManagerImpl to restore the state. Instantiate the Fragment and assign the bundle that stores the state to the mSavedFragmentState member.

Restoring the data and view tree with the mSavedFragmentState member during the Fragment’s lifecycle state growth from creation to display will call the corresponding lifecycle callback method with mSavedFragmentState as an input parameter. Then clear mSavedFragmentState. Fragment subclasses restore custom data in onCreate and onActivityCreated.