Android x fragment exploration - transaction operation

Article directory

Summary

In normal development, the scene where Fragment is often used is to create Fragment and add it to the specified layout container of FragmentActivity. To achieve such an operation, first obtain the FragmentManager, then open the transaction FragmentTransaction, add add, remove, replace, hide, show and other operations, and finally commit XXX to perform the corresponding operations.

Next, go to the source tracing process to see how fragment manager schedules execution.

Source code exploration

The source code is based on 'Android x.fragment: fragment: 1.1.0'

The origin of fragment Manager

In FragmentActivity, get FragmentManager by getSupportFragmentManager method:

[FragmentActivity.java]

public FragmentManager getSupportFragmentManager() {
    return mFragments.getSupportFragmentManager();
}

In this method, the following is obtained through the mFragments member:
[FragmentController.java]

public FragmentManager getSupportFragmentManager() {
    return mHost.mFragmentManager;
}

It is obtained through the mHost member.

Before studying fragment manager, let's see what mFragments and mHost are.

FragmentController

First, let's look at the mFragments members of the FragmentActivity:

[FragmentActivity.java]

public class FragmentActivity extends ComponentActivity implements
        ActivityCompat.OnRequestPermissionsResultCallback,
        ActivityCompat.RequestPermissionsRequestCodeValidator {
    // ···
    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
    // ···
}

mFragments is a FragmentController object.

Next, take a look at the createController method. Here, pass in the HostCallbacks instance:
[FragmentController.java]

public static FragmentController createController(@NonNull FragmentHostCallback<?> callbacks) {
    return new FragmentController(checkNotNull(callbacks, "callbacks == null"));
}

private FragmentController(FragmentHostCallback<?> callbacks) {
    // Hold HostCallbacks reference
    mHost = callbacks;
}

HostCallbacks

When creating the FragmentController, the HostCallbacks are instantiated and assigned to the mHost member of the FragmentController.

[FragmentActivity.java]

class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
        ViewModelStoreOwner,
        OnBackPressedDispatcherOwner {
    public HostCallbacks() {
        super(FragmentActivity.this /*fragmentActivity*/);
    }
    // ···
}

HostCallbacks inherit from FragmentHostCallback, which is the internal class of FragmentActivity and holds the reference of FragmentActivity.

[FragmentHostCallback.java]

public abstract class FragmentHostCallback<E> extends FragmentContainer {
    // ···
    FragmentHostCallback(@NonNull FragmentActivity activity) {
        this(activity, activity /*context*/, new Handler(), 0 /*windowAnimations*/);
    }

    FragmentHostCallback(@Nullable Activity activity, @NonNull Context context,
            @NonNull Handler handler, int windowAnimations) {
        // Hold FragmentActivity context reference
        mActivity = activity;
        mContext = Preconditions.checkNotNull(context, "context == null");
        // Handler created in constructor, running in main thread by default
        mHandler = Preconditions.checkNotNull(handler, "handler == null");
        // Animation related, default is 0
        mWindowAnimations = windowAnimations;
    }
    // ···
}

You can see that HostCallbacks holds the current FragmentActivity context and creates a main thread Handler.

FragmentManagerImpl

FragmentManager is an abstract class, while FragmentManagerImpl is its implementation class:

[FragmentManagerImpl.java]

final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
    // ···
}

FragmentManagerImpl is created when FragmentHostCallback is instantiated:
[FragmentHostCallback.java]

public abstract class FragmentHostCallback<E> extends FragmentContainer {
    // ···
    final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
    // ···
}

The mFragmentManager member of the FragmentHostCallback holds the FragmentManagerImpl.

Binding of FragmentManagerImpl

In the onCreate method of FragmentActivity:
[FragmentActivity.java]

protected void onCreate(@Nullable Bundle savedInstanceState) {
    // Call attachHost method of FragmentController, pass null as parameter
    mFragments.attachHost(null /*parent*/);
    // ···
}

Next, look at the attachHost method:
[FragmentController.java]

public void attachHost(@Nullable Fragment parent) {
    // Call the attachController method of FragmentManagerImpl and pass in the HostCallbacks instance. The parameter parent is null.
    mHost.mFragmentManager.attachController(
            mHost, mHost /*container*/, parent);
}

Enter the attachController method of FragmentManagerImpl:
[FragmentManagerImpl.java]

public void attachController(@NonNull FragmentHostCallback host,
        @NonNull FragmentContainer container, @Nullable final Fragment parent) {
    if (mHost != null) throw new IllegalStateException("Already attached");
    // Hold HostCallbacks reference
    mHost = host;
    mContainer = container;
    mParent = parent;
    if (mParent != null) {
        // Since the callback depends on us being the primary navigation fragment,
        // update our callback now that we have a parent so that we have the correct
        // state by default
        updateOnBackPressedCallbackEnabled();
    }
    // Set up the OnBackPressedCallback
    // Omit the BackPressed settings section
    // ···
    
    // Get the FragmentManagerViewModel
    // Omit ViewModel settings section
    // ···
}

FragmentActivity binds FragmentManagerImpl in onCreate, receives the HostCallbacks instance in the attachController method of FragmentManagerImpl and saves it.

Relationship among FragmentController, HostCallbacks, FragmentManagerImpl

FragmentActivity holds FragmentController reference, FragmentController holds HostCallbacks reference, HostCallbacks holds FragmentActivity reference, FragmentActivity and FragmentManagerImpl hold reference to each other.

FragmentActivity obtains HostCallbacks through FragmentController, and then calls FragmentManagerImpl indirectly through HostCallbacks. FragmentManagerImpl indirectly obtains context and executes callback methods through HostCallbacks.

Add transaction action

Open transaction

After getting the FragmentManagerImpl instance, open a transaction through its beginTransaction method:
[FragmentManagerImpl.java]

public FragmentTransaction beginTransaction() {
    return new BackStackRecord(this);
}

Here, create a BackStackRecord (inherited from the FragmentTransaction, and implement the BackStackEntry and OpGenerator interfaces). The BackStackRecord will hold the FragmentManagerImpl.

Fragment transaction encapsulates a transaction that contains a set of operations (single or multiple operations).

Add add Fragment

Here, for example, add a Fragment to the FragmentActivity.

In general, to add Fragment, call the add method of FragmentTransaction to pass in the layout ID and Fragment instance:
[FragmentTransaction.java]

public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment,
        @Nullable String tag) {
    // Tag optional parameter, which can be used to find fragment through tag, and the op add identifies the add operation type
    doAddOp(containerViewId, fragment, tag, OP_ADD);
    return this;
}

BackStackRecord overrides the doadop method:
[BackStackRecord.java]

void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
    super.doAddOp(containerViewId, fragment, tag, opcmd);
    // Assign the held FragmentManagerImpl to the fragmentmanager member of the Fragment
    fragment.mFragmentManager = mManager;
}

In this method, the doadop of FragmentTransaction is called by super or by FragmentTransaction. Only after the execution is completed, the mFragmentManager member of Fragment is assigned a value.

[FragmentTransaction.java]

void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
    final Class<?> fragmentClass = fragment.getClass();
    final int modifiers = fragmentClass.getModifiers();
    // Check whether the fragment is an anonymous class, or non public access, or defined in another class without declaring static
    if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
            || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
        throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                + " must be a public static class to be  properly recreated from"
                + " instance state.");
    }

    if (tag != null) {
        // Check whether the fragment has a tag but is different from the current one
        if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
            throw new IllegalStateException("Can't change tag of fragment "
                    + fragment + ": was " + fragment.mTag
                    + " now " + tag);
        }
        // fragment save tag
        fragment.mTag = tag;
    }

    // containerViewId is the layout container ID used to add this fragment in the FragmentActivity
    if (containerViewId != 0) {
        if (containerViewId == View.NO_ID) {
            throw new IllegalArgumentException("Can't add fragment "
                    + fragment + " with tag " + tag + " to container view with no id");
        }
        // Check whether the fragment has set the container ID but is different from the ID currently passed in
        if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
            throw new IllegalStateException("Can't change container ID of fragment "
                    + fragment + ": was " + fragment.mFragmentId
                    + " now " + containerViewId);
        }
        // fragment save container ID, mFragmentId is container ID by default
        fragment.mContainerId = fragment.mFragmentId = containerViewId;
    }
    
    // Add operation
    addOp(new Op(opcmd, fragment));
}

The following steps are done in this method:

  1. In this method, we first verify our customized Fragment:
  • Cannot be an anonymous class
  • Accessibility must be public
  • If it is a member class, it must be a static class
  1. Then check whether the tag (if any) is different from the existing saved fragment, and finally save it in the fragment
  2. Then check the layout ID (if there is any incoming), which is the ViewGroup used to host the fragment in the FragmentActivity. There must be a setting ID. if there are already settings in the fragment, they must be the same. Finally, the fragment saves the container ID, and the mFragmentId is also the container ID by default
  3. Encapsulating Op saves the operation type and fragment and adds it to the operation set
Op

Op represents an operation. See its constructor:

[FragmentTransaction.java]

Op(int cmd, Fragment fragment) {
    // Save operation type
    this.mCmd = cmd;
    // Save the fragment to be operated
    this.mFragment = fragment;
    // The lifecycle state is set to RESUMED, which corresponds to the state after the Activity performs onResume
    this.mOldMaxState = Lifecycle.State.RESUMED;
    this.mCurrentMaxState = Lifecycle.State.RESUMED;
}
addOp

[FragmentTransaction.java]

void addOp(Op op) {
    // Save Op object
    mOps.add(op);
    // Animation related, defaults to 0
    op.mEnterAnim = mEnterAnim;
    op.mExitAnim = mExitAnim;
    op.mPopEnterAnim = mPopEnterAnim;
    op.mPopExitAnim = mPopExitAnim;
}

The mpops member of the FragmentTransaction is used to save the Op object, which is ArrayList.

Submission of affairs

FragmentTransaction has four methods to commit a transaction: commit, commitAllowingStateLoss, commitNow, commitnowallowstateloss. These four methods are abstract classes, which are implemented in BackStackRecord.

Take commitAllowingStateLoss as an example:
[BackStackRecord.java]

public int commitAllowingStateLoss() {
    return commitInternal(true);
}

int commitInternal(boolean allowStateLoss) {
    if (mCommitted) throw new IllegalStateException("commit already called");
    // Omit DEBUG section···
    mCommitted = true;
    // mAddToBackStack defaults to false, true if addToBackStack method is called
    if (mAddToBackStack) {
        mIndex = mManager.allocBackStackIndex(this);
    } else {
        mIndex = -1;
    }
    // At this time, the incoming allowStateLoss is true
    // Join the team through FragmentManagerImpl
    mManager.enqueueAction(this, allowStateLoss);
    return mIndex;
}

Next, look at the enqueueAction method of FragmentManagerImpl, and pass in BackStackRecord itself and true:
[FragmentManagerImpl.java]

public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
    if (!allowStateLoss) {
        checkStateLoss();
    }
    synchronized (this) {
        if (mDestroyed || mHost == null) {
            if (allowStateLoss) {
                // This FragmentManager isn't attached, so drop the entire transaction.
                return;
            }
            throw new IllegalStateException("Activity has been destroyed");
        }
        if (mPendingActions == null) {
            mPendingActions = new ArrayList<>();
        }
        // mPendingActions represents the set of actions to be executed, and action is BackStackRecord (implementing the OpGenerator interface)
        mPendingActions.add(action);
        scheduleCommit();
    }
}

Next, look at the scheduleCommit method:
[FragmentManagerImpl.java]

void scheduleCommit() {
    synchronized (this) {
        // Mark whether there is a delayed transaction, which does not exist by default
        boolean postponeReady =
                mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
        // Mark whether there is only one transaction to be executed, which is qualified when the transaction is submitted through enqueueAction for the first time
        boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
        if (postponeReady || pendingReady) {
            // If the conditions are met, the task is scheduled to execute through the Handler
            mHost.getHandler().removeCallbacks(mExecCommit);
            mHost.getHandler().post(mExecCommit);
            updateOnBackPressedCallbackEnabled();
        }
    }
}

When there is a delayed transaction or the first transaction is submitted, the Handler will post a Runnable to perform the operation. Here, the Handler is the main thread Handler created when the HostCallbacks is instantiated.

It can be seen that the transaction submitted through the commit and commitalowingstateloss methods will not be executed immediately, but wait for the next time the main thread LOOPER takes out the message.

Processing transaction operations

The msexcommit using Handler post in the previous article is a Runnable:
[FragmentManagerImpl.java]

Runnable mExecCommit = new Runnable() {
    @Override
    public void run() {
        execPendingActions();
    }
};

When triggered, the execPendingActions method is called to process the transaction operation:
[FragmentManagerImpl.java]

public boolean execPendingActions() {
    // Check status and initialize mTmpRecords, mTmpIsPop collection, and handle deferred transactions
    ensureExecReady(true);

    boolean didSomething = false;
    // Generate commands and save them in the mTmpRecords, mTmpIsPop collection
    while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
        // Mark is currently in execution transaction
        mExecutingActions = true;
        try {
            // Remove or merge redundant operations and perform
            removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
        } finally {
            cleanupExec();
        }
        didSomething = true;
    }

    updateOnBackPressedCallbackEnabled();
    // Execute delayed start Fragment
    doPendingDeferredStart();
    // Clean up the maactive collection
    burpActive();

    return didSomething;
}

In this method, the pending transactions are retrieved and executed in a while loop. When generateOpsForPendingActions returns false to indicate that there are no pending transactions, the while loop is ended.

Description of two sets:

  • mTmpRecords: save the backstackrecords to be executed.
  • mTmpIsPop: indicates whether the BackStackRecord corresponding to the index of mTmpRecords is of add type or remove type. The default value is false. When backing back through popBackStack, the corresponding index position will be marked as true.

generateOpsForPendingActions

[FragmentManagerImpl.java]

private boolean generateOpsForPendingActions(ArrayList<BackStackRecord> records,
                                             ArrayList<Boolean> isPop) {
    // Mark whether there is an execution transaction
    boolean didSomething = false;
    synchronized (this) {
        // Return false if there is no pending transaction
        if (mPendingActions == null || mPendingActions.size() == 0) {
            return false;
        }

        final int numActions = mPendingActions.size();
        // Traverse mPendingActions. At this time, there is only one BackStackRecord of OP? Add type
        for (int i = 0; i < numActions; i++) {
            // Call generateOps method in turn
            didSomething |= mPendingActions.get(i).generateOps(records, isPop);
        }
        // Empty mPendingActions
        mPendingActions.clear();
        // Remove msexcommit to avoid redundant execution
        mHost.getHandler().removeCallbacks(mExecCommit);
    }
    return didSomething;
}

generateOps

Here, take adding Fragment as an example. At this time, there is a BackStackRecord of OP? Add type in mPendingActions. Check its generateOps method:
[BackStackRecord.java]

public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
    if (FragmentManagerImpl.DEBUG) {
        Log.v(TAG, "Run: " + this);
    }

    // records collection adding BackStackRecord itself
    records.add(this);
    // isRecordPop add false
    isRecordPop.add(false);
    // mAddToBackStack is false by default, so fallback stack is not added
    if (mAddToBackStack) {
        mManager.addBackStackState(this);
    }
    return true;
}

removeRedundantOperationsAndExecute

[FragmentManagerImpl.java]

private void removeRedundantOperationsAndExecute(ArrayList<BackStackRecord> records,
                                                 ArrayList<Boolean> isRecordPop) {
    if (records == null || records.isEmpty()) {
        return;
    }

    if (isRecordPop == null || records.size() != isRecordPop.size()) {
        throw new IllegalStateException("Internal error with the back stack records");
    }

    // Force start of any postponed transactions that interact with scheduled transactions:
    executePostponedTransaction(records, isRecordPop);

    final int numRecords = records.size();
    int startIndex = 0;
    for (int recordNum = 0; recordNum < numRecords; recordNum++) {
        // Mark whether operation sorting optimization is supported. The default value is false
        final boolean canReorder = records.get(recordNum).mReorderingAllowed;
        if (!canReorder) {
            // execute all previous transactions
            if (startIndex != recordNum) {
                executeOpsTogether(records, isRecordPop, startIndex, recordNum);
            }
            // execute all pop operations that don't allow reordering together or
            // one add operation
            int reorderingEnd = recordNum + 1;
            // All non pop transactions return false
            if (isRecordPop.get(recordNum)) {
                while (reorderingEnd < numRecords
                        && isRecordPop.get(reorderingEnd)
                        && !records.get(reorderingEnd).mReorderingAllowed) {
                    reorderingEnd++;
                }
            }
            // Execute the BackStackRecord in the specified index interval
            executeOpsTogether(records, isRecordPop, recordNum, reorderingEnd);
            startIndex = reorderingEnd;
            recordNum = reorderingEnd - 1;
        }
    }
    if (startIndex != numRecords) {
        // This if condition may be met when the operation sorting optimization is turned on
        executeOpsTogether(records, isRecordPop, startIndex, numRecords);
    }
}
Operation sorting optimization

When there are multiple pending transactions, fragment manager deletes some redundant transaction operations. For example:

  • Suppose that two transactions are executed together for the same layout container. One transaction adds a Fragment A, and the next transaction replaces it with Fragment B. After optimization, the first operation will be cancelled and only Fragment B will be added.
  • Suppose there are three transactions. One adds Fragment A, the second adds Fragment B, and the third removes Fragment A. After optimization, Fragment A will not be added or deleted, only Fragment B will be added.

Sorting optimization operation will lead to behaviors beyond the expectation of developers, so this operation is not performed by default.

executeOpsTogether

Enter the executeOpsTogether method:
[FragmentManagerImpl.java]

private void executeOpsTogether(ArrayList<BackStackRecord> records,
                                ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
    // ···
    executeOps(records, isRecordPop, startIndex, endIndex);
    // ···
}

Enter the executeOps method:
[FragmentManagerImpl.java]

private static void executeOps(ArrayList<BackStackRecord> records,
                               ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
    for (int i = startIndex; i < endIndex; i++) {
        final BackStackRecord record = records.get(i);
        final boolean isPop = isRecordPop.get(i);
        if (isPop) {
            record.bumpBackStackNesting(-1);
            // Only execute the add operations at the end of
            // all transactions.
            boolean moveToState = i == (endIndex - 1);
            // Performing a fallback operation
            record.executePopOps(moveToState);
        } else {
            record.bumpBackStackNesting(1);
            // Execute the executeOps with the type of OP \
            record.executeOps();
        }
    }
}

executeOps

Look at the op "add case in the executeOps method of BackStackRecord:
[BackStackRecord.java]

void executeOps() {
    final int numOps = mOps.size();
    // Traverse the operations in the transaction
    for (int opNum = 0; opNum < numOps; opNum++) {
        final Op op = mOps.get(opNum);
        final Fragment f = op.mFragment;
        if (f != null) {
            f.setNextTransition(mTransition, mTransitionStyle);
        }
        switch (op.mCmd) {
            case OP_ADD:
                f.setNextAnim(op.mEnterAnim);
                // Add Fragment
                mManager.addFragment(f, false);
                break;
            // Omit other case s···
            default:
                throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
        }
        if (!mReorderingAllowed && op.mCmd != OP_ADD && f != null) {
            mManager.moveFragmentToExpectedState(f);
        }
    }
    // Mreordering allowed is false by default
    if (!mReorderingAllowed) {
        // Added fragments are added at the end to comply with prior behavior.
        // Update the status. Here, the current status and true of the FragmentManagerImpl are passed in
        mManager.moveToState(mManager.mCurState, true);
    }
}

In this method, when dealing with OP_ADD, first add the fragment to the collection through the addFragment method, and then update the state and set the fragment through moveToState.

addFragment

[FragmentManagerImpl.java]

public void addFragment(Fragment fragment, boolean moveToStateNow) {
    if (DEBUG) Log.v(TAG, "add: " + fragment);
    // Add fragment to the maactive collection for saving. If setRetainInstance is set, it will also be added to the fragment managerviewmodel for saving
    makeActive(fragment);
    // At this time, it is the OP_ADD operation, and mDetached is false
    if (!fragment.mDetached) {
        if (mAdded.contains(fragment)) {
            throw new IllegalStateException("Fragment already added: " + fragment);
        }
        synchronized (mAdded) {
            // fragment joins the mmadded set
            mAdded.add(fragment);
        }
        // Mmadded is used to mark whether the fragment has been added to the mmadded set
        fragment.mAdded = true;
        // mRemoving marks whether the fragment is removed from the Activity
        fragment.mRemoving = false;
        // At this time, mView has not been generated, null
        if (fragment.mView == null) {
            fragment.mHiddenChanged = false;
        }
        if (isMenuAvailable(fragment)) {
            mNeedMenuInvalidate = true;
        }
        // At this time, the moveToStateNow passed in is false
        if (moveToStateNow) {
            moveToState(fragment);
        }
    }
}

Add fragment s to the maactive and mAdded collections.

moveToState

Back to the BackStackRecord.executeOps method, execute the moveToState method at the end of the method:

[FragmentManagerImpl.java]

void moveToState(int newState, boolean always) {
    if (mHost == null && newState != Fragment.INITIALIZING) {
        throw new IllegalStateException("No activity");
    }

    if (!always && newState == mCurState) {
        return;
    }

    // Update the state, and the incoming newState and mCurState are equal
    mCurState = newState;

    // Must add them in the proper order. mActive fragments may be out of order
    final int numAdded = mAdded.size();
    // Traverse mmadded, take out Fragment processing in turn
    for (int i = 0; i < numAdded; i++) {
        Fragment f = mAdded.get(i);
        moveFragmentToExpectedState(f);
    }

    // Ellipsis.
}

In this method, Fragment is taken out in turn, and moveFragmentToExpectedState is called to initialize the Fragment according to the final expected state.

The mCurState member records the current state of the FragmentManagerImpl. The state values are:

  • INITIALIZING (0): initial state, default state
  • CREATED (1): start-up status, corresponding to onCreate and onDestroyView phases
  • Activity "created (2): above created and under started status, corresponding to onStart and onStop stages
  • Started (3): above started and under resumed phases, corresponding to onResume and onPause phases
  • Resumed (4): the status above resumed, corresponding to the onResume completion stage

See the moveFragmentToExpectedState method:
[FragmentManagerImpl.java]

void moveFragmentToExpectedState(Fragment f) {
    if (f == null) {
        return;
    }
    if (!mActive.containsKey(f.mWho)) {
        if (DEBUG) {
            Log.v(TAG, "Ignoring moving " + f + " to state " + mCurState
                    + "since it is not added to " + this);
        }
        return;
    }
    int nextState = mCurState;
    if (f.mRemoving) {
        if (f.isInBackStack()) {
            nextState = Math.min(nextState, Fragment.CREATED);
        } else {
            nextState = Math.min(nextState, Fragment.INITIALIZING);
        }
    }
    // Schedule the fragment to the corresponding phase of the target state and perform the initialization settings of the corresponding phase
    moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false);

    if (f.mView != null) {
        // ···
    }
    if (f.mHiddenChanged) {
        completeShowHideFragment(f);
    }
}

Another moveToState overload method is invoked in this method:
[FragmentManagerImpl.java]

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
                 boolean keepActive) {
    // Omit status check and verification part
    // ···
    
    // Judge whether the life cycle state of fragment is equal to that of fragment manager. The newly created fragment state defaults to INITIALIZING
    if (f.mState <= newState) {
        // ···
        switch (f.mState) {
            case Fragment.INITIALIZING:
                // Start the attach phase
                if (newState > Fragment.INITIALIZING) {
                    if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
                    // Omit mSavedFragmentState section···

                    f.mHost = mHost;
                    f.mParentFragment = mParent;
                    f.mFragmentManager = mParent != null
                            ? mParent.mChildFragmentManager : mHost.mFragmentManager;

                    // If we have a target fragment, push it along to at least CREATED
                    // so that this one can rely on it as an initialized dependency.
                    // Omit mTarget section···
                    // Omit mtargethwho section···

                    dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
                    f.performAttach();
                    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);
                        f.performCreate(f.mSavedFragmentState);
                        dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
                    } else {
                        f.restoreChildFragmentState(f.mSavedFragmentState);
                        f.mState = Fragment.CREATED;
                    }
                }
                // fall through
                // Note that there is no break here, and will continue to match the case execution
            case Fragment.CREATED:
                // Start to enter the CreateView phase
                // 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) {
                            if (f.mContainerId == View.NO_ID) {
                                throwException(new IllegalArgumentException(
                                        "Cannot create fragment "
                                                + f
                                                + " for a container view with no id"));
                            }
                            // Get the layout container through FragmentActivity.this.findViewById(f.mContainerId)
                            container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
                            if (container == null && !f.mRestored) {
                                String resName;
                                try {
                                    resName = f.getResources().getResourceName(f.mContainerId);
                                } catch (Resources.NotFoundException e) {
                                    resName = "unknown";
                                }
                                throwException(new IllegalArgumentException(
                                        "No view found for id 0x"
                                                + Integer.toHexString(f.mContainerId) + " ("
                                                + resName
                                                + ") for fragment " + f));
                            }
                        }
                        f.mContainer = container;
                        // Get layoutingger from FragmentActivity context in performgetlayoutingger
                        // The onCreateView method of fragment will be triggered in performCreateView,
                        // Return our customized view and assign it to mView members
                        f.performCreateView(f.performGetLayoutInflater(
                                f.mSavedFragmentState), container, f.mSavedFragmentState);
                        if (f.mView != null) {
                            f.mInnerView = f.mView;
                            f.mView.setSaveFromParentEnabled(false);
                            if (container != null) {
                                // Add the view of fragment to the layout container
                                container.addView(f.mView);
                            }
                            if (f.mHidden) {
                                f.mView.setVisibility(View.GONE);
                            }
                            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 changed
                            f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
                                    && f.mContainer != null;
                        } else {
                            f.mInnerView = null;
                        }
                    }

                    f.performActivityCreated(f.mSavedFragmentState);
                    dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
                    if (f.mView != null) {
                        f.restoreViewState(f.mSavedFragmentState);
                    }
                    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) {
        // Omit the reverse growth part of life cycle, i.e. onPause, onStop, onDestroyView, onDestroy, onDetach
        // ···
    }
    
    if (f.mState != newState) {
        Log.w(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
                + "expected state " + newState + " found " + f.mState);
        // Update fragment status
        f.mState = newState;
    }
}

In the CREATED state phase of moveToState method, the Fragment view is CREATED (if there is a custom view), and then added to the given layout container.

The moveToState method also schedules the state update of Fragment according to the current life cycle state of Fragment and Fragment manager, and triggers the life cycle callback method of each phase of Fragment.

summary

The previous article roughly tracks the process from creating a Fragment to adding it to the FragmentActivity.
First, the Fragment manager encapsulates the "add Fragment" behavior into an Op operation, then adds the Op to a transaction BackStackRecord, then adds the transaction to the queue to be executed for saving, and then schedules it to the main thread for execution through the Handler.
Fragment manager then traverses the transactions in the queue to be executed and executes them in turn. In the execution process, according to the operation type, fragment state and fragment manager state, the processing and lifecycle callback of the corresponding phase of fragment are executed.

Published 31 original articles, won praise 2, visited 8758
Private letter follow

Tags: Fragment Java Android

Posted on Sun, 15 Mar 2020 02:12:44 -0400 by bri4n