An overview of the

Fragment represents a behavior or user interface part of an Activity. You can combine multiple fragments into one Activity to build a multi-paned UI, and reuse a Fragment in multiple activities. You can think of a Fragment as a modular part of an Activity that has its own life cycle, can receive its own input events, and can be added or removed while the Activity is running. A Fragment must always be embedded in an Activity and its life cycle is directly influenced by the host Activity life cycle. For example, when an Activity is paused, all fragments within it are also paused; When an Activity is destroyed, all fragments are also destroyed. However, while the Activity is running (in its restored life cycle state), you can manipulate each Fragment independently, such as adding or removing them. When you perform such a Fragment transaction, you can also add it to the return stack managed by the Activity – each return stack entry in the Activity is a record of a Fragment transaction that has occurred. The back stack allows the user to undo the Fragment transaction (back) by pressing the back button.

When you add a Fragment as part of an Activity layout, it exists inside a ViewGroup in the Activity view hierarchy, and the Fragment defines its own view layout. You can insert the Fragment as a < Fragment > element into your Activity layout by declaring it in the Activity layout file, or by adding it to an existing ViewGroup with application code. However, fragments don’t have to be part of the Activity layout; You can also use fragments that do not have their own UI as invisible worker threads for activities.

This article will analyze the source code to get a deeper understanding of the creation, destruction and life cycle of fragments.

It is recommended that readers read the source code analysis of Fragment management before reading this article to have a clear understanding of the Fragment management class.

Analysis of the entrance

    /** * construct and display Fragment **@paramContainerViewId Id of a container control *@paramCLZ fragments * /
    protected void showFragment(@IdRes int containerViewId, Class<? extends Fragment> clz) {
        FragmentManager fm = getFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();// Start transaction management
        try {
            Fragment f = clz.newInstance();
            ft.add(containerViewId, f, clz.getName());// Add operations
            ft.commit();// Commit the transaction
        } catch(Exception e) { e.printStackTrace(); }}Copy the code

This code dynamically adds a Fragment to containerViewId and displays it. This code is involved in the Fragment management. See the Fragment management source code.

The code analysis

BackStackRecord#run

After calling commit, the real execution is in the Run method of BackStackRecord:

    public void run(a) {...if (mManager.mCurState >= Fragment.CREATED) {
            SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
            SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
            calculateFragments(firstOutFragments, lastInFragments);
            beginTransition(firstOutFragments, lastInFragments, false);
        }
        // Iterate through the linked list, processing transactions according to the CMD transaction type
        Op op = mHead;
        while(op ! =null) {
            switch (op.cmd) {
                case OP_ADD: {
                    // Add a new Fragment
                    Fragment f = op.fragment;
                    f.mNextAnim = op.enterAnim;
                    mManager.addFragment(f, false);
                }
                break;
                case OP_REPLACE: {
                    Fragment f = op.fragment;
                    int containerId = f.mContainerId;
                    if(mManager.mAdded ! =null) {
                        for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
                            Fragment old = mManager.mAdded.get(i);
                            if (old.mContainerId == containerId) {
                                if (old == f) {
                                    op.fragment = f = null;
                                } else {
                                    if (op.removed == null) {
                                        op.removed = new ArrayList<Fragment>();
                                    }
                                    op.removed.add(old);
                                    old.mNextAnim = op.exitAnim;
                                    if (mAddToBackStack) {
                                        old.mBackStackNesting += 1; } mManager.removeFragment(old, mTransition, mTransitionStyle); }}}}if(f ! =null) {
                        f.mNextAnim = op.enterAnim;
                        mManager.addFragment(f, false); }}break;
                case OP_REMOVE: {
                    Fragment f = op.fragment;
                    f.mNextAnim = op.exitAnim;
                    mManager.removeFragment(f, mTransition, mTransitionStyle);
                }
                break;
                case OP_HIDE: {
                    Fragment f = op.fragment;
                    f.mNextAnim = op.exitAnim;
                    mManager.hideFragment(f, mTransition, mTransitionStyle);
                }
                break;
                case OP_SHOW: {
                    Fragment f = op.fragment;
                    f.mNextAnim = op.enterAnim;
                    mManager.showFragment(f, mTransition, mTransitionStyle);
                }
                break;
                case OP_DETACH: {
                    Fragment f = op.fragment;
                    f.mNextAnim = op.exitAnim;
                    mManager.detachFragment(f, mTransition, mTransitionStyle);
                }
                break;
                case OP_ATTACH: {
                    Fragment f = op.fragment;
                    f.mNextAnim = op.enterAnim;
                    mManager.attachFragment(f, mTransition, mTransitionStyle);
                }
                break;
                default: {
                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
                }
            }

            op = op.next;
        }

        mManager.moveToState(mManager.mCurState, mTransition,
                mTransitionStyle, true);

        if (mAddToBackStack) {
            mManager.addBackStackState(this); }}Copy the code

Since we are calling the add operation, the code snippet executed is:

    case OP_ADD: {
        Fragment f = op.fragment;
        f.mNextAnim = op.enterAnim;
        mManager.addFragment(f, false);
    }
    break;
Copy the code

Parameter Description:

  • Op. fragment: An instance of the fragment created in the showFragment, and the fragment mTag, mFragmentId, and mContainerId are already initialized
  • Op. enterAnim: Entry animation, you can ignore it
  • MManager: FragmentManagerImpl instance

FragmentManagerImpl#addFragment

    public void addFragment(Fragment fragment, boolean moveToStateNow) {
        // Added Fragment list
        if (mAdded == null) {
            mAdded = new ArrayList<Fragment>();
        }

        // Set the mIndex for the Fragment and add the Fragment to the mActive list
        makeActive(fragment);
        
        // Determine whether to detach. The default is false
        if(! fragment.mDetached) {if (mAdded.contains(fragment)) {
                throw new IllegalStateException("Fragment already added: " + fragment);
            }
            // Add Fragment to mAdded list
            mAdded.add(fragment);
            // Set the Fragment bit
            fragment.mAdded = true;
            fragment.mRemoving = false;
            // Determine whether the menu needs to be refreshed
            if (fragment.mHasMenu && fragment.mMenuVisible) {
                mNeedMenuInvalidate = true;
            }
            MoveToStateNow is false in this analysis, and the moveToState method is called in the outer method
            if(moveToStateNow) { moveToState(fragment); }}}Copy the code

AddFragment to the mActive and mAdded lists, and set the Fragment. MAdded to true and the Fragment. MRemoving to false. After performing the ADD operation, execute moveToState, which, as the name suggests, changes the Fragment to a certain state

    // The state of mmanager. mCurState is important
    mManager.moveToState(mManager.mCurState, mTransition,
            mTransitionStyle, true);

    // Add this operation to the rollback stack
    if (mAddToBackStack) {
        mManager.addBackStackState(this);
    }
Copy the code

Fragment state

The Fragment life cycle depends on the Activity. For example, if the Activity is in onResume state, the Fragment will also be in onResume state.

    static final int INVALID_STATE = -1;   // Invalid state used as a null value.
    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 initial state of mCurState is fragment.initializing. What is the value of mCurState when calling moveToState in the BackStackRecord? It changes depending on the Activity lifecycle, so let’s look at the FragmentActivity code

    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        / / bind FragmentManager
        mFragments.attachHost(null /*parent*/);
        
        super.onCreate(savedInstanceState); . .// Distribute the Create event of the Fragment
        mFragments.dispatchCreate();
    }
Copy the code
    public void dispatchCreate(a) {
        mHost.mFragmentManager.dispatchCreate();
    }
    
    public void dispatchCreate(a) {
        mStateSaved = false;
        // Notice that the new state is set
        moveToState(Fragment.CREATED, false);
    }
    
    void moveToState(int newState, boolean always) {
        moveToState(newState, 0.0, always);
    }
    
    void moveToState(int newState, int transit, int transitStyle, boolean always) {... .// Assign to mCurStatemCurState = newState; . . }Copy the code

MCurState is changed to fragment. CREATED in onCreate. This state is also changed when other Activity lifecycle methods are called back.

  • OnCreate: fragments. CREATED
  • OnStart: Fragment.ACTIVITY_CREATED–> fragment. STARTED (fragment. ACTIVITY_CREATED is only triggered once after the Activity is created, Fragment.STARTED Will be triggered every time onStart starts)
  • OnResume: fragments. RESUMED
  • OnPause: fragments. STARTED
  • OnStop: fragments. STOPPED
  • OnDestroy: fragments. The INITIALIZING

Here is a state transition diagram:

So as the Activity life cycle progresses, so does the life cycle of all fragments within the Activity. From the time the Activity is created to the time it is displayed, it will end up in the onResume state. So let’s say now that mCurState is Fragment.RESUMED,

Let’s keep tracking FragmentManagerImpl

FragmentManagerImpl#moveToState

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

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

        mCurState = newState;
        if(mActive ! =null) {
            boolean loadersRunning = false;
            // Search for all Active fragments and change the status of all fragments
            for (int i=0; i<mActive.size(); i++) {
                Fragment f = mActive.get(i);
                if(f ! =null) {
                    // Key code
                    moveToState(f, newState, transit, transitStyle, false);
                    if(f.mLoaderManager ! =null) { loadersRunning |= f.mLoaderManager.hasRunningLoaders(); }}}if(! loadersRunning) { startPendingDeferredFragments(); }// Let the Activity refresh the Menu
            if(mNeedMenuInvalidate && mHost ! =null && mCurState == Fragment.RESUMED) {
                mHost.onInvalidateOptionsMenu();
                mNeedMenuInvalidate = false; }}}Copy the code

The RESUMED state is equal to fragment.resumed Iterating through the Fragment stored in the mActive list to change the Fragment state, this calls a moveToState method, which is where the actual Fragment lifecycle is called

    void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {

        // If Fragments are not added to the mAdded list by detach or Fragment, set the new state of the target Fragment to CREATED. This branch will not be entered in this analysis
        if((! f.mAdded || f.mDetached) && newState > Fragment.CREATED) { newState = Fragment.CREATED; }// In this analysis, F.removing is false
        if (f.mRemoving && newState > f.mState) {
            // While removing a fragment, we can't change it to a higher state.
            newState = f.mState;
        }
        // Whether to start late
        if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
            newState = Fragment.STOPPED;
        }
        
        if (f.mState < newState) {
            // Branch of this hit.// Select a branch of the case based on the current state of the Fragment. Note that there is no break statement in the switch case. This design allows the Fragment to push its own state sequentially into the target state
            switch (f.mState) {
                case Fragment.INITIALIZING:
                    if(f.mSavedFragmentState ! =null) {... } f.mHost = mHost;//mParent is passed in by calling attachHost in the FragmentActivity onCreate method with an empty valuef.mParentFragment = mParent; f.mFragmentManager = mParent ! =null
                            ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
                    f.mCalled = false;
                    // The onAttach callback will set mCalled to true
                    f.onAttach(mHost.getContext());
                    if(! f.mCalled) {throw new SuperNotCalledException("Fragment " + f
                                + " did not call through to super.onAttach()");
                    }
                    
                    if (f.mParentFragment == null) {
                        // Let the Activity listen for the attach of the Fragment
                        mHost.onAttachFragment(f);
                    } else {
                        f.mParentFragment.onAttachFragment(f);
                    }

                    // f.retaining defaults to false
                    if(! f.mRetaining) {// Critical code, internal call [Fragment lifecycle] onCreate
                        f.performCreate(f.mSavedFragmentState);
                    } else {
                        f.restoreChildFragmentState(f.mSavedFragmentState, true);
                        f.mState = Fragment.CREATED;
                    }
                    f.mRetaining = false;
                    
                    // Whether the Fragment is defined in the < Fragment > tag of the Layout file. This time, the Fragment is added dynamically to the code
                    if (f.mFromLayout) {
                        // For fragments that are part of the content view
                        // layout, we need to instantiate the view immediately
                        // and the inflater will take care of adding it.
                        f.mView = f.performCreateView(f.getLayoutInflater(
                                f.mSavedFragmentState), null, f.mSavedFragmentState);
                        if(f.mView ! =null) {
                            f.mView.setSaveFromParentEnabled(false);
                            if(f.mHidden) f.mView.setVisibility(View.GONE); f.onViewCreated(f.mView, f.mSavedFragmentState); }}// Notice that there is no break
                case Fragment.CREATED:
                    if (newState > Fragment.CREATED) {
                        if(! f.mFromLayout) {// Start creating the Fragment view
                            ViewGroup container = null;
                            if(f.mContainerId ! =0) {
                                if (f.mContainerId == View.NO_ID) {
                                    throwException(new IllegalArgumentException(""));
                                }
                                
                                // Call the Activity's findViewById method to find the control
                                container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
                                if (container == null && !f.mRestored) {
                                    ......
                                }
                            }
                            f.mContainer = container;
                            // Call the Fragment life cycle onCreateView and return the view newly created in the Fragment
                            f.mView = f.performCreateView(f.getLayoutInflater(
                                    f.mSavedFragmentState), container, f.mSavedFragmentState);
                            if(f.mView ! =null) {
                                f.mView.setSaveFromParentEnabled(false);
                                if(container ! =null) {
                                    // Set the entry animation
                                    Animator anim = loadAnimator(f, transit, true,
                                            transitionStyle);
                                    if(anim ! =null) {
                                        anim.setTarget(f.mView);
                                        setHWLayerAnimListenerIfAlpha(f.mView, anim);
                                        anim.start();
                                    }
                                    // Add the Fragment view to the parent control
                                    container.addView(f.mView);
                                }
                                if (f.mHidden) f.mView.setVisibility(View.GONE);
                                
                                // [Fragment lifecycle] onViewCreated callbackf.onViewCreated(f.mView, f.mSavedFragmentState); }}// The Fragment lifecycle onActivityCreated is called internally
                        f.performActivityCreated(f.mSavedFragmentState);
                        if(f.mView ! =null) {
                            f.restoreViewState(f.mSavedFragmentState);
                        }
                        f.mSavedFragmentState = null;
                    }
                case Fragment.ACTIVITY_CREATED:
                    if (newState > Fragment.ACTIVITY_CREATED) {
                        f.mState = Fragment.STOPPED;
                    }
                case Fragment.STOPPED:
                    if (newState > Fragment.STOPPED) {
                        if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                        // Critical code that internally calls Fragment lifecycle onStart
                        f.performStart();
                    }
                case Fragment.STARTED:
                    if (newState > Fragment.STARTED) {
                        // Critical code that calls Fragment lifecycle onResume internally
                        f.performResume();
                        // Get rid of this in case we saved it and never needed it.
                        f.mSavedFragmentState = null;
                        f.mSavedViewState = null; }}}else if (f.mState > newState) {
            //state is degraded. }if (f.mState != newState) {
            f.mState = newState;
        }
    }
Copy the code

The logic of this code is still quite long, so I wrote the comments in the code. As you can see, this code is cleverly written to push the Fragment lifecycle layer by layer through the switch case. For example, if the current fragnemt state is Fragment.STARTED, it will only execute performResume. If the Fragment state is fragment. INITIALIZING, the Fragment life cycle onAttach–>onResume is called from the beginning of the switch. A brief description of the above code:

  • MHost is an instance of the FragmentHostCallback abstract class, whose implementation class is HostCallbacks for the Activity
  • MParent null
  • Mhost. getContext() gets the context that is the host Activity instance
  • Views created in the Fragment are automatically added to the parent control via container.addView(f.view)

Many fragments have their life cycles called through the performXxx() method of the Fragment, for example:

    void performCreate(Bundle savedInstanceState) {... onCreate(savedInstanceState); . }View performCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {...return onCreateView(inflater, container, savedInstanceState);
    }

    void performActivityCreated(Bundle savedInstanceState) {... onActivityCreated(savedInstanceState); . }void performStart(a) {... onStart(); . }void performResume(a) {... onResume(); . }Copy the code

Fragment Operations to degrade the Fragment state

OnAttach ->onResume callback (); onAttach->onResume callback (); Let’s go back to that code

    if (f.mState < newState) {
        ......
    } else if (f.mState > newState) {
        //state is degraded. }Copy the code

The answer is in the else if branch, for example when Acivity locks the screen, the Activity life cycle automatically calls onPause, which causes dispatchPause, which calls moveToState(Fragment.STARTED, false); Because the Fragment’s current state is RESUMED, which is larger than newState, it will branch off from the else if, triggering the appropriate lifecycle method. The logic of the else if branch is similar to that of the state upgrade, so we’ll analyze it here

The life cycle

conclusion

This article analyzes the process of Fragment creation and lifecycle callback from the perspective of source code. If readers are interested in Fragment operations such as remove, replace, hide, detach, attach, etc., they can analyze them by themselves. The core code is in the Run method of the BackStackRecord class and in the moveToState method of FragmentManagerImpl.