The source code in this article is based on the Support package version 27.1.1

1 Fragment Life cycle

Everyone knows about the Fragment lifecycle and its corresponding lifecycle functions:

Fragment
Fragment

static final int INITIALIZING = 0;     // Not yet created.
static final int CREATED = 1;          // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3;          // Fully created, not started.
static final int STARTED = 4;          // Created and started, not resumed.
static final int RESUMED = 5;          // Created started and resumed.
Copy the code

The Fragment’s entire life cycle flows through these six states, calling the corresponding lifecycle method and then entering the next state, as shown in the figure below

1.1 fragments and Activity

The Fragment lifecycle is closely related to the Activity lifecycle. An Activity manages the Fragment lifecycle by calling the corresponding method of the FragmentManager in the Activity lifecycle method, Migrate existing fragments to the next state using the FragmentManager and fire the appropriate lifecycle functions

Activity life cycle function Function triggered by FragmentManager Fragment state migration Fragment life cycle callback
onCreate dispatchCreate INITIALIZING->

CREATED
OnAttach, onCreate
onStart dispatchStart CREATED->

ACTIVITY_CREATED->

STOPPED->

STARTED
OnCreateView, onActivityCreated, onStart
OnResume (to be precise, onPostResume) dispatchResume STARTED->

RESUMED
onResume
onPause dispatchPause RESUMED->

STARTED
onPause
onStop dispatchStop STARTED->

STOPPED
onStop
onDestroy dispatchDestroy STOPPED->

ACTIVITY_CREATED->

CREATED->

INITIALIZING
OnDestroyView, onDestroy, onDetach

The previous image is even clearer:

1.2 with FragmentTransaction fragments

We often use FragmentTransaction methods like Add, remove, replace, attach, detach, hide, and show to operate on a Fragment. These methods change the state of the Fragment and trigger the corresponding lifecycle function

(Assuming the Activity is in RESUME state)

Methods in FragmentTransaction Life cycle function triggered by Fragment
add onAttach->

onCreate->

onCreateView->

onActivityCreated->

onStart->

onResume
remove onPause->

onStop->

onDestoryView->

onDestory->

onDetach
replace Replace can be split into add and remove,
detach (You need to add the Fragment before calling detach)

onPause->

onStop->

onDestoryView
attach (You need to call detach before calling Attach)

onCreateView->

onActivityCreated->

onStarted->

onResumed
hide No lifecycle functions are triggered
show No lifecycle functions are triggered

By observing changes in the Fragment life cycle, we can easily see that add/remove causes the Fragment to migrate between INITIALIZING and RESUMED states. The attach/detach operation causes the Fragment to migrate between the CREATED and RESUMED states.

Note: One important thing to note about the add function is that if your Fragment is in the STARTED state, it can’t go to any state until it’s RESUMED. Then trigger onResume – > FragmentManager. DispatchStateChange (fragments. RESUMED), then calls the fragments. After onResume function fragments into a state will be RESUMED.

1.3 fragments with the ViewPager

With FragmentPagerAdapter we can combine fragments with ViewPager. What is the life cycle of a Fragment in a ViewPager?

The FragmentPagerAdapter operates on the Fragment using FragmentTransaction, including add, detach, and attach.

@SuppressWarnings("ReferenceEquality")
@Override
public Object instantiateItem(ViewGroup container, int position) {
    / /...
    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if(fragment ! =null) {
        // If there is an existing Fragment instance
        // Add by attach
        mCurTransaction.attach(fragment);
    } else {
        // The Fragment instance has not been created yet. Create an instance using getItem
        // Then add with the add operation
        fragment = getItem(position);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }
    / /...
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    / /...
    // Use detach to destroy the Fragment
    mCurTransaction.detach((Fragment)object);
}
Copy the code

As you can see from the source code above, the FragmentPagerAdapter adds fragments through the FragmentTransaction.add method, and then by attaching and detach. The corresponding life cycle of these methods can be referred to the figure above. Let’s simulate this for example. Suppose we have ViewPager with 5 pages, and offscreenPageLimit is 1,

  1. The first time it loads, the first and second pages passaddThe function is loaded atRESUMEDstate
  2. Slide to the second page and the third page is loaded, also throughaddThe function is loaded atRESUMEDstate
  3. Continue to slide to page 3, where the first page passesdetachThe function is recycled atCREATEDStatus, while page 4 passesaddBe loaded inRESUMEDstate
  4. Slide to the second page, when the first page passesattachLoaded, inRESUMEDStatus, page four bydetachIn aCREATEDstate

Summary: In the ViewPager, the current page and the left and right pages of the current page are all in the RESUMED state. Other pages are either not CREATED or are CREATED. The Fragment lifecycle changes during a slide can be seen in the example above.

1.4 with DialogFragment fragments

When using DialogFragment, we are used to using its show and hide methods to display or hide. Internally, these two methods use the Add and remove methods of FragmentTransaction, whose lifecycle we’ve already covered.

public void show(FragmentManager manager, String tag) {
    mDismissed = false;
    mShownByMe = true;
    FragmentTransaction ft = manager.beginTransaction();
    // Core operations
    ft.add(this, tag);
    ft.commit();
}


void dismissInternal(boolean allowStateLoss) {
    / /...
    if (mBackStackId >= 0) {
        / /...
    } else {
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        // Core operations
        ft.remove(this);
        if (allowStateLoss) {
            ft.commitAllowingStateLoss();
        } else{ ft.commit(); }}}Copy the code

DialogFragment is special in that it maintains a Dialog internally. At the beginning of DialogFragment design, it uses FragmentManager to manage Dialog, mainly using three methods of Dialog: show, hide, and dismiss. The corresponding relationship is as follows

Fragment life cycle function Method of the corresponding Dialog
onStart show
onStop hide
onDestoryView dismiss

2 What are the effects of different methods of adding fragments on their life cycle

Fragment can be added in two ways:

  1. Add it by using the fragment tag in the XML file
  2. Use in codeFragmentTransactionadd

Let’s talk about how these two different additions can affect the Fragment’s lifecycle callbacks.

2.1 Using the Fragment Label to Add data

The creation of Fragment instances in XML is ultimately left to the FragmentManager with the onCreateView method

//FragmentManager.java
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    // Check whether it is a Fragment label
    if (!"fragment".equals(name)) {
        return null;
    }

    // The following code is defined in the fetch XML
    //Fragment some information
    // Such as class name (full path), ID, tag
    String fname = attrs.getAttributeValue(null."class");
    TypedArray a =  context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
    if (fname == null) {
        fname = a.getString(FragmentTag.Fragment_name);
    }
    int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
    String tag = a.getString(FragmentTag.Fragment_tag);
    a.recycle();

    // Check whether the specified Fragment class is derived from a Fragment
    if(! Fragment.isSupportFragmentClass(mHost.getContext(), fname)) {return null;
    }
    
    // The ID of the Container containing the Fragment must be not empty, the tag is not empty, or the ID of the Container containing the Fragment is not empty
    // Otherwise an exception is thrown
    intcontainerId = parent ! =null ? parent.getId() : 0;
    if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
        throw new IllegalArgumentException(attrs.getPositionDescription()
                + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
    }

    // If we restored from a previous state, we may already have
    // instantiated this fragment from the state and should use
    // that instance instead of making a new one.Fragment fragment = id ! = View.NO_ID ? findFragmentById(id) :null;
    if (fragment == null&& tag ! =null) {
        fragment = findFragmentByTag(tag);
    }
    if (fragment == null&& containerId ! = View.NO_ID) { fragment = findFragmentById(containerId); }//log...
    
    // Create a Fragment instance with reflection
    if (fragment == null) {
        fragment = Fragment.instantiate(context, fname);
        // This field marks that the Fragment instance is from an XML file
        fragment.mFromLayout = true; fragment.mFragmentId = id ! =0 ? id : containerId;
        fragment.mContainerId = containerId;
        fragment.mTag = tag;
        fragment.mInLayout = true;
        fragment.mFragmentManager = this;
        fragment.mHost = mHost;
        fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
        // Focus on methods
        // The second parameter is called moveToStateNow
        // This is true, so the Fragment will be immediate
        // Migrate to the current state recorded by FragmentManager
        // Usually we set layout in the onCreate method
        // So in general this is the FragmentManager
        // CREATED state
        addFragment(fragment, true);

    } else if (fragment.mInLayout) {
        / /...
    } else {
        / /...
    }

    if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
        // If FragmentManager is currently in INITIALIZING state
        // Force the Fragment to be migrated to the CREATED state
        moveToState(fragment, Fragment.CREATED, 0.0.false);
    } else {
        // If the FragmentManager state is greater than CREATED
        // Then migrate the Fragment to the corresponding state
        moveToState(fragment);
    }

    / /...
    return fragment.mView;
}
Copy the code

The job of onCreateView is basically to create the Fragment instance and migrate it to the specified state. We use a process that normally starts an Activity as a scenario for analysis, and the Fragment will eventually enter the CREATED state.

When we looked at the Fragment life cycle, we mentioned that when an Activity enters onCreate, it triggers the Fragment’s onAttach and onCreate life cycle callback. In this case, however, the Fragment will trigger onCreateView to create the view in advance, as can be seen in moveToState’s source code:

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
            
    / /...
     switch (f.mState) {
        case Fragment.INITIALIZING:
            / /...
        case Fragment.CREATED:
            / /...
            / / the following if statement from ensureInflatedFragmentView method
            // The method code is pasted here for convenience
            // If the Fragment comes from a layout file
            // Then trigger onCreateView to create the attempted instance
             if(f.mFromLayout && ! f.mPerformedCreateView) { f.mView = f.performCreateView(f.performGetLayoutInflater( f.mSavedFragmentState),null, f.mSavedFragmentState);
                if(f.mView ! =null) {
                    f.mInnerView = f.mView;
                    f.mView.setSaveFromParentEnabled(false);
                    if (f.mHidden) f.mView.setVisibility(View.GONE);
                    f.onViewCreated(f.mView, f.mSavedFragmentState);
                    dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false);
                } else {
                    f.mInnerView = null; }}if (newState > Fragment.CREATED) {
                / /...
            }
        / /...
     }
    / /...

}
Copy the code

2.2 Add FragmentTransaction in your code

Here we use adding a Fragment in the activity. onCreate method as the analysis scenario

public class DemoActivity extends FragmentActivity{
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.demo);
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(R.id.container, newDemoFragment()); ft.commit(); }}Copy the code

Regardless of what happens in add, we know that if we don’t call commit, add won’t work. Commit method will experience the following invocation chain commit – > commitInternal – > FragmentManager. EnqueueAction

// The implementation class for FragmentTransaction is BackStackRecord
// The actual type of action is BackStackRecord
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if(! allowStateLoss) { checkStateLoss(); }synchronized (this) {
            / /...
            mPendingActions.add(action);
            synchronized (this) {
                booleanpostponeReady = mPostponedTransactions ! =null && !mPostponedTransactions.isEmpty();
                booleanpendingReady = mPendingActions ! =null && mPendingActions.size() == 1;
                if (postponeReady || pendingReady) {
                    / / the key
                    //getHandler gets a main thread Handler
                    // Instead of calling moveToState directly, one is thrown
                    // Message to message queue, which will cause the Fragment state migration to be delayedmHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); }}}}Copy the code

When triggered mExecCommit will experience the following invocation chain FragmentManager. ExecPendingActions – > BackStackRecord. GenerateOps – >… – > BackStackRecord. ExecuteOps – > FragmentManager. XxxFragment – > FragmentManager. MoveToState fragments happened eventually state transition

Does mExecCommit really just sit in the message queue waiting to be executed? The answer is no. Let’s look at the FragmentActivity.onStart method

protected void onStart(a) {
    super.onStart();
    / /...
    
    / / on the blackboard
    mFragments.execPendingActions();

    / /...

    mFragments.dispatchStart();
    / /...
}
Copy the code

As you can see, execPendingActions are triggered early, and then dispatchStart, The Fragment will be migrated from INITIALIZING to STARTED(the execPendingActions method will remove the mExecCommit from the message queue when triggered). FragmentActivity in onStart, onResume and onPostResume lifecycle callback will be called FragmentManager. ExecPendingActions, So when we add fragments to our code in activity. onStart and activity. onResume, the Fragment state transitions will occur after activity. onResume and activity. onPostResume, respectively. So what happens when you add a Fragment after onPostResume? . At this time due to onPostResume method of FragmentManager execPendingActions had already in the super call, therefore mExecCommit will be triggered, One of the biggest differences here is that the Fragment life cycle changes in a different message cycle than the Activity life cycle.

2.3 summarize

Let’s conclude this section with a picture:

The STOPPED state was removed from the support package for version 28.0.0, but the life changes were consistent with the above figure when tested