[toc]

The introduction

Fragment is an Android component that is created using a Fragment. How does the Fragment display on the screen and call back the corresponding life cycle?

getSupportFragmentManager().beginTransaction()
    .add(id, Fragment.instantiate(this, Fragment.class.getName(),
        bundle), Fragment.TAG)
    .commit();
Copy the code

This process can be broken down into four steps.

Instantiate (this, fragment.class.getName ())

2, obtain SupportFragmentManager: getSupportFragmentManager ()

BeginTransaction ().add()

4, perform this operation: commit()


1, Fragment creation

/**
 * Create a new instance of a Fragment with the given class name.  This is
 * the same as calling its empty constructor.
 *
 * @param context The calling context being used to instantiate the fragment.
 * This is currently just used to get its ClassLoader.
 * @param fname The class name of the fragment to instantiate.
 * @param args Bundle of arguments to supply to the fragment, which it
 * can retrieve with {@link #getArguments()}.  May be null.
 * @return Returns a new fragment instance.
 * @throws InstantiationException If there is a failure in instantiating
 * the given fragment class.  This is a runtime exception; it is not
 * normally expected to happen.
 */
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
    try{ Class<? > clazz = sClassMap.get(fname);if (clazz == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = context.getClassLoader().loadClass(fname);
            sClassMap.put(fname, clazz);
        }
        Fragment f = (Fragment) clazz.getConstructor().newInstance();
        if(args ! =null) {
            args.setClassLoader(f.getClass().getClassLoader());
            f.setArguments(args);
        }
        returnf; }}Copy the code

Summary: The creation process will first check the cache to see if the fragment has been loaded. If not, it will simply call the fragment constructor with no arguments through reflection.


2, getSupportFragmentManager ()

//FragmentActivity.java
/** * Return the FragmentManager for interacting with fragments associated * with this activity. */
public FragmentManager getSupportFragmentManager(a) {
    return mFragments.getSupportFragmentManager();
}
Copy the code

Get the supportFragmentManager for mFragments

final FragmentController mFragments = FragmentController.createController(new HostCallbacks())
Copy the code

The mFragments here are a member variable of the FragmentActivity and are instantiated when the FragmentActivity is created

Who new the Activity -> the Activity start process

public class FragmentController {
    private finalFragmentHostCallback<? > mHost;public static FragmentController createController(FragmentHostCallback
        callbacks) {
        return new FragmentController(callbacks);
    }

    private FragmentController(FragmentHostCallback
        callbacks) {
        mHost = callbacks;
    }

    public FragmentManager getSupportFragmentManager(a) {
        returnmHost.getFragmentManagerImpl(); }}Copy the code

GetSupportFragmentManager returned is actually a getFragmentManagerImpl mHost () object. This class is an internal class in FragmentActivity, so you can use fragment.this directly

class HostCallbacks extends FragmentHostCallback<FragmentActivity> {
    public HostCallbacks(a) {
        super(FragmentActivity.this /*fragmentActivity*/); }}Copy the code

HostCallBack inherits from FragmentHostCallBack, which holds objects such as Activity, context, handler, etc. It can communicate with Fragment and Activity. The FragmentManger returned in mHost is actually the FragmentManagerImpl member variable.

public abstract class FragmentHostCallback<E> extends FragmentContainer {
    @Nullable private final Activity mActivity;
    @NonNull private final Context mContext;
    @NonNull private final Handler mHandler;
    private final int mWindowAnimations;
    final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
}
Copy the code

Conclusion: getSupportFragmentManager () returns the actual is FragmentManagerImpl this object, in which all the basic operation of the fragments are implemented. FragmentManagerImpl is a very important class that we end up calling for both and Fragment operations, as described later.


3, beginTransaction (). The add ()

//FragmentManagerImpl.java
@Override
public FragmentTransaction beginTransaction(a) {
    return new BackStackRecord(this);
}
Copy the code
final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry.FragmentManagerImpl.OpGenerator{

  final FragmentManagerImpl mManager;
	public BackStackRecord(FragmentManagerImpl manager) { mManager = manager; }}Copy the code

A BackStackRecord object is created, where this is passed in as an instance object of FragmentManagerImpl. BackStackRecord is a concrete implementation class that operates on FragmentTransaction. FragmentTransaction defines our operations on fragments, such as add, remove, replace, and so on. The add and remove operations are actually performed in a transaction, and the commit is actually performed by the mManger.

//BackStackRecord.java
@Override
public FragmentTransaction add(int containerViewId, Fragment fragment) {
    doAddOp(containerViewId, fragment, null, OP_ADD);
    return this;
}
Copy the code

Add (int containerViewId, Fragment Fragment) is called in doAddOp(containerViewId, Fragment, NULL, OP_ADD). DoAddOp, as its name implies, is the process of adding an Op to a transaction, which can be understood as each of our BackStackRecord transactions, is a process composed of multiple ops. So methods like replace(),remove(), and so on all add an Op, but different Op types are different. Let’s look at doAddOp(containerViewId, fragment, null, OP_ADD).

//BackStackRecord.java
private void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
    final Class fragmentClass = fragment.getClass();
    final int modifiers = fragmentClass.getModifiers();
    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.");
    }

    fragment.mFragmentManager = mManager;

    if(tag ! =null) {
        if(fragment.mTag ! =null && !tag.equals(fragment.mTag)) {
            throw new IllegalStateException("Can't change tag of fragment "
                    + fragment + ": was " + fragment.mTag
                    + " now " + tag);
        }
        fragment.mTag = tag;
    }

    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");
        }
        if(fragment.mFragmentId ! =0&& fragment.mFragmentId ! = containerViewId) {throw new IllegalStateException("Can't change container ID of fragment "
                    + fragment + ": was " + fragment.mFragmentId
                    + " now " + containerViewId);
        }
        fragment.mContainerId = fragment.mFragmentId = containerViewId;
    }

    addOp(new Op(opcmd, fragment));
}
Copy the code

Check whether the Fragment’s modifier is public, whether the Fragment’s tag already exists, and where it contains containerViewId. Save this information to Framgment. Finally, addOp(new Op(opcmd, fragment)) was called.

// BackStackRecord.java
		ArrayList<Op> mOps = new ArrayList<>();
    void addOp(Op op) {
        mOps.add(op);
        op.enterAnim = mEnterAnim;
        op.exitAnim = mExitAnim;
        op.popEnterAnim = mPopEnterAnim;
        op.popExitAnim = mPopExitAnim;
    }
Copy the code

It’s just adding a record to the mOps List here. Here we find that for each Op, enterAnim, exitAnim, popEnterAnim

PopExitAnim and popExitAnim are all assigned via BackStackRecord variables, Set with public FragmentTransaction setCustomAnimations(int Enter, int exit,int popEnter, int popExit), where you want to keep animations consistent in a transaction.

Bottom line: beginTransaction starts a transaction and then calls Add to add an Op to the collection. A transaction can consist of multiple Ops.


4, the commit ()

// BackStackRecord.java
@Override
public int commit(a) {
    return commitInternal(false);
}
Copy the code

After commit(), false is passed in as commitInternal(Boolean allowStateLoss).

// BackStackRecord.java
int commitInternal(boolean allowStateLoss) {
    if (mCommitted) throw new IllegalStateException("commit already called");
    mCommitted = true;
    if (mAddToBackStack) {
        mIndex = mManager.allocBackStackIndex(this);
    } else {
        mIndex = -1;
    }
    mManager.enqueueAction(this, allowStateLoss);
    return mIndex;
}
Copy the code

In this method, flag bits are used to determine whether the COMMIT has already been executed. Then judge the mAddToBackStack argument, which is false by default when we call

// BackStackRecord.java
@Override
public FragmentTransaction addToBackStack(@Nullable String name) {
    if(! mAllowAddToBackStack) {throw new IllegalStateException(
                "This FragmentTransaction is not allowed to be added to the back stack.");
    }
    mAddToBackStack = true;
    mName = name;
    return this;
}
Copy the code

Method, after the sign position are true, you can guess mManager. AllocBackStackIndex (this) these rocks should handle the back button back transaction logic. EnqueueAction (this, allowStateLoss) will end up in mManager.enqueueAction(this, allowStateLoss) anyway.

//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<>();
        }
      	// Add the BackStackRecord to this collectionmPendingActions.add(action); scheduleCommit(); }}private void checkStateLoss(a) {
        if (isStateSaved()) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
        if(mNoTransactionsBecause ! =null) {
            throw new IllegalStateException(
                    "Can not perform this action inside of "+ mNoTransactionsBecause); }}Copy the code

This method does a state check if allowStateLoss is passed false. Can not Perform This action after onSaveInstanceState. It is possible that some user action caused onSaveInstanceState() to be followed by a call to commit(), where FragmentManger would throw an exception if it did not save the current state. The general commitAllowingStateLoss () calls this method to avoid this exception, but it is not certain that it will be a problem in some scenarios.

After that, the mPendingActions collection is created and an action is added to it, which is the OpGenerator interface that BackStackRecord implements. ScheduleCommit () is then called

//FragmentManagerImpl.java
/**
 * Schedules the execution when one hasn't been scheduled already. This should happen
 * the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when
 * a postponed transaction has been started with
 * {@link Fragment#startPostponedEnterTransition()}
 */
@SuppressWarnings("WeakerAccess") /* synthetic access */
void scheduleCommit(a) {
    synchronized (this) {
        booleanpostponeReady = mPostponedTransactions ! =null && !mPostponedTransactions.isEmpty();
        booleanpendingReady = mPendingActions ! =null && mPendingActions.size() == 1;
        if (postponeReady || pendingReady) {
          	// Perform a cancel operation to ensure that only one Runnable is executing.
            mHost.getHandler().removeCallbacks(mExecCommit);
            mHost.getHandler().post(mExecCommit);
        }
    }
}
 Runnable mExecCommit = new Runnable() {
        @Override
        public void run(a) { execPendingActions(); }};Copy the code

The end result is mhost. gethandler. post(mExecCommit). The handler here, the handler from HostCallBack mentioned earlier, is provided by FragmentActivity. The Runnable runs in the main thread through this handler.

Why do we have to run this procedure on the main thread here?

//FragmentManagerImpl.java
/** * Only call from main thread! * /
public boolean execPendingActions(a) {
  	// Check the status before execution
    ensureExecReady(true);
    boolean didSomething = false;
     / / generateOpsForPendingActions will judge whether the current task and cycles
    while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
        mExecutingActions = true;
        try {
            removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
        } finally {
            cleanupExec();
        }
        didSomething = true;
    }

    doPendingDeferredStart();
    burpActive();

    return didSomething;
}
Copy the code

The method executed on the main thread first calls ensureExecReady(True), which makes some state judgments, such as whether the Activity is on the main thread and whether the current Activity has been destroyed. Then go to generateOpsForPendingActions (mTmpRecords mTmpIsPop)

//FragmentManagerImpl.java
/**
 * Adds all records in the pending actions to records and whether they are add or pop
 * operations to isPop. After executing, the pending actions will be empty.
 *
 * @param records All pending actions will generate BackStackRecords added to this.
 *                This contains the transactions, in order, to execute.
 * @param isPop All pending actions will generate booleans to add to this. This contains
 *              an entry for each entry in records to indicate whether or not it is a
 *              pop action.
 */
private boolean generateOpsForPendingActions(ArrayList
       
         records, ArrayList
        
          isPop)
        
        {
    boolean didSomething = false;
    synchronized (this) {
        if (mPendingActions == null || mPendingActions.size() == 0) {
            return false;
        }

        final int numActions = mPendingActions.size();
        for (int i = 0; i < numActions; i++) {
          // mPendingActions are the actions passed in the enqueueAction(OpGenerator Action, Boolean allowStateLoss) method from the beginning, i.e. the BackStackRecord object
            didSomething |= mPendingActions.get(i).generateOps(records, isPop);
        }
        mPendingActions.clear();
        mHost.getHandler().removeCallbacks(mExecCommit);
    }
    return didSomething;
}

// BackStackRecord.java
 public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
        records.add(this);
        isRecordPop.add(false);
        if (mAddToBackStack) {
            mManager.addBackStackState(this);
        }
        return true;
    }
Copy the code

Combined with a generateOpsForPendingActions annotation, this code is relatively easy to understand. First we added ourselves as an action to the mPendingActions collection at BackStackRecord.com MIT (). GenerateOps (Records, isPop) is called to each action interface, passing in the first collection mTMPRErecords to indicate that transactions need to be executed, and directly adding the BackStack object itself. The second set indicates whether the transaction is a POP operation, adds a false directly to the BackStackRecord, and the entire method returns Trure.

Back to execPendingActions () method, the implementation of removeRedundantOperationsAndExecute (mTmpRecords mTmpIsPop) method

//FragmentManagerImpl.java
// From the comments, this method can optimize transaction execution:
// For example, if one transaction performs add and then another performs POP, it will be optimized to remove unnecessary operations.
// Similarly, two committed transactions executed simultaneously will be optimized to remove redundant operations as well as two POP operations executed simultaneously.
// It is assumed that the loop handles such an operation.
private void removeRedundantOperationsAndExecute(ArrayList
       
         records, ArrayList
        
          isRecordPop)
        
        {
    // 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++) {
        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;
            if (isRecordPop.get(recordNum)) {
                while(reorderingEnd < numRecords && isRecordPop.get(reorderingEnd) && ! records.get(reorderingEnd).mReorderingAllowed) { reorderingEnd++; } } executeOpsTogether(records, isRecordPop, recordNum, reorderingEnd); startIndex = reorderingEnd; recordNum = reorderingEnd -1; }}if (startIndex != numRecords) {
        executeOpsTogether(records, isRecordPop, startIndex, numRecords);
    }
}
Copy the code

Except where the loop is rearranged, executeOpsTogether(Records, isRecordPop, startIndex, recordNum) will eventually be called

//FragmentManagerImpl.java
private void executeOpsTogether(ArrayList<BackStackRecord> records,
        ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) ExecuteOps (Records, isRecordPop, startIndex, endIndex); ...}Copy the code
//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);
            record.executePopOps(moveToState);
        } else {
            record.bumpBackStackNesting(1);
          	// Perform the operationrecord.executeOps(); }}}Copy the code
// BackStackRecord.java
void executeOps(a) {
    final int numOps = mOps.size();
    for (int opNum = 0; opNum < numOps; opNum++) {
        final Op op = mOps.get(opNum);
        final Fragment f = op.fragment;
        if(f ! =null) {
            f.setNextTransition(mTransition, mTransitionStyle);
        }
        switch (op.cmd) {
            case OP_ADD:
                f.setNextAnim(op.enterAnim);
								// Add to mActive collection
                mManager.addFragment(f, false);
                break;
        }
        if(! mReorderingAllowed && op.cmd ! = OP_ADD && f ! =null) { mManager.moveFragmentToExpectedState(f); }}if(! mReorderingAllowed) {// Added fragments are added at the end to comply with prior behavior.
      	// Synchronize the status of the Manger -> FragmentActivity
        mManager.moveToState(mManager.mCurState, true); }}Copy the code

You end up in executeOps() based on the call trajectory, using the add operation as an example. Add the generated Fragment to the mActivie collection in FragmentManger.

Finally, the Manager’s moveToState is called, where the state value passed in to the Manager comes from the FragmentActivity’s lifecycle callback

The pictures from the Internet are not very accurate

@SuppressWarnings("ReferenceEquality")
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
        boolean keepActive) {· · · · · ·switch (f.mState) {
            case Fragment.INITIALIZING:
								  // Predispatch the callback of fragment Attach
                  // Call onAttach on the fragment
                  // Prefragment callback to fragment onCreate
                  // Call onCreate on the fragment
                  // Move the fragment to the next state
            case Fragment.CREATED:
									// Find the Id where the Fragment was added
                  // Call the fragment's onCreateView
                  // Add objects created by the Fragment to the container location
                  // Container is a ViewGroup object
                  // Call onViewCreated on the fragment......}}else if (f.mState > newState) {
  			// This handles the case where the fragment lifecycle is earlier than the Manger lifecycle, keeping the Fragment and Activiy in sync
        switch (f.mState) {
            case Fragment.RESUMED:
                if (newState < Fragment.RESUMED) {
                    f.performPause();
                    dispatchOnFragmentPaused(f, false);
                }
                // fall through
            case Fragment.STARTED:
                if (newState < Fragment.STARTED) {
                    f.performStop();
                    dispatchOnFragmentStopped(f, false); } · · · · · ·}}Copy the code

MoveToState is the core method in a Fragment that synchronizes the Fragment’s lifecycle with the Activity’s. There are two situations where the fragment life cycle is lagging behind the activity; 2. Fragments have an earlier life cycle than activities. Normally we create fragments in case 1.

In case 1, you do something different by comparing framgent to the activity lifecycle

For example, when the fragment’s life cycle is INITIALIZING

if (newState > Fragment.INITIALIZING) {
                  // Predispatch the callback of fragment Attach
                    dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
                    f.mCalled = false;
                  	// Call onAttach on the fragment
                    f.onAttach(mHost.getContext());
                    dispatchOnFragmentAttached(f, mHost.getContext(), false);
                    if(! f.mIsCreated) {// Prefragment callback to fragment onCreate
                        dispatchOnFragmentPreCreated(f, f.mSavedFragmentState, false);
                      	// Call onCreate on the fragment
                        f.performCreate(f.mSavedFragmentState);
                        dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
                    } else {
                            f.restoreChildFragmentState(f.mSavedFragmentState);
                      			// Move the fragment to the next statef.mState = Fragment.CREATED; }}Copy the code

First, the fragment attach event is distributed. Then, the onAttach and Create methods of the fragment are called back. Finally, the fragment state is moved to Created and the flag bit is changed to ensure that the process will not be repeated. So when the Activity is INITIALIZING, it actually corresponds to onAttach and onCreated of the fragment. Note that swtich has no break after each case, so execute the above logic and proceed to the second judgment. When the fragment lifecycle is CREATED, if the activity lifecycle is ahead of the fragment lifecycle, go through the following process.

    if (newState > Fragment.CREATED) {
                    if(! f.mFromLayout) { ViewGroup container =null;
                        if(f.mContainerId ! =0) {
                          	// Find the Id where the Fragment was added
                            container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
                        }
                        f.mContainer = container;
                      // Call the fragment's onCreateView
                        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 objects created by the Fragment to the container location
                              	// Container is a ViewGroup object
                                container.addView(f.mView);
                            }
                            if (f.mHidden) {
                                f.mView.setVisibility(View.GONE);
                            }
                          	// Call onViewCreated on the fragment
                            f.onViewCreated(f.mView, f.mSavedFragmentState);
                            dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
                                    false);
                        } else {
                            f.mInnerView = null; }}// Call the fragment's onActivityCreated
                    f.performActivityCreated(f.mSavedFragmentState);
                 	 	// Distribute events
                    dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
                }
Copy the code

This is the key code for the fragment display. We first look for the placeholder View based on the id we passed in. Then we call the Fragment onCreateView. The view is then added to the Container to display the view, and the callback completes its lifecycle.

Conclusion:

Commit () first performs some state judgments, which may throw an IllegalStateException(“Can not Perform this action after onSaveInstanceState”). The general commitAllowingStateLoss () calls this method to avoid this exception, but it is not certain that it will be a problem in some scenarios.

2. The transaction is then added to the queue, and the activity’s handler runs the specific execution to the main thread

3. Optimize the set of transactions first, and then iterate through each transaction

4. Synchronize the state of the Activity with the state of the fragment in moveToState. All fragment lifecycle callbacks are called in this method. In the CREATED state, onCreateView of the Fragment is called to generate the displayed view, and the view is added to the specified id for display.


Summary of the whole process:

1. Framgnet creation: The process of creating a framgnet will look in the cache to see if the fragment has been loaded. If it has not been created, it will simply call the framgnet constructor with no arguments through reflection.

2, getSupportFragmentManager () : getSupportFragmentManager () returns the actual is FragmentManagerImpl this object, in which all the basic operation of the fragments are implemented. FragmentManagerImpl is a very important class that we end up calling for both fragment operations.

3, beginTransaction().add() : beginTransaction starts a transaction and then calls add to add an Op to the collection. A transaction can consist of multiple ops.

4, the commit () : Commit () first makes some state judgments, where IllegalStateException(“Can not Perform this action after onSaveInstanceState”) may be thrown. The general commitAllowingStateLoss () calls this method to avoid this exception, but it is not certain that it will be a problem in some scenarios. The transaction is then added to the queue, and the activity’s handler runs the specific execution to the main thread. The execution process optimizes the set of transactions before iterating through each transaction. MoveToState synchronizes the state of the Activity with the state of the fragment. All fragment lifecycle callbacks are called in this method. In the CREATED state, onCreateView of the Fragment is called to generate the displayed view, and the view is added to the specified id for display.

legacy

1, Who new Activity -> Activity start process

2. Why must commit be performed on the main thread

3. How is transaction instruction optimization handled