A Fragment,

1.1 Life Cycle

Fragment declaration period on the official website.

Figure 1 Fragment lifecycle flowchart

In addition to the announcement period, also can register FragmentLifecycleCallbacks to insert more Fragment state monitoring, specific include:

public abstract static class FragmentLifecycleCallbacks {
        /**
         * Called right before the fragment's {@linkFragment#onAttach(Context)} method is called. * This is a good time to inject any required dependencies or perform other  configuration * for the fragment before any of the fragment's lifecycle methods are invoked. * *@param fm Host FragmentManager
         * @param f Fragment changing state
         * @param context Context that the Fragment is being attached to
         */
        public void onFragmentPreAttached(@NonNull FragmentManager fm, @NonNull Fragment f,
                @NonNull Context context) {}

        /**
         * Called after the fragment has been attached to its host. Its host will have had
         * <code>onAttachFragment</code> called before this call happens.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param context Context that the Fragment was attached to
         */
        public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f,
                @NonNull Context context) {}

        /**
         * Called right before the fragment's {@link Fragment#onCreate(Bundle)} method is called.
         * This is a good time to inject any required dependencies or perform other configuration
         * for the fragment.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param savedInstanceState Saved instance bundle from a previous instance
         */
        public void onFragmentPreCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
                @Nullable Bundle savedInstanceState) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onCreate(Bundle)}. This will only happen once for any given
         * fragment instance, though the fragment may be attached and detached multiple times.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param savedInstanceState Saved instance bundle from a previous instance
         */
        public void onFragmentCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
                @Nullable Bundle savedInstanceState) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onActivityCreated(Bundle)}. This will only happen once for any given
         * fragment instance, though the fragment may be attached and detached multiple times.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param savedInstanceState Saved instance bundle from a previous instance
         */
        public void onFragmentActivityCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
                @Nullable Bundle savedInstanceState) {}

        /**
         * Called after the fragment has returned a non-null view from the FragmentManager's
         * request to {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment that created and owns the view
         * @param v View returned by the fragment
         * @param savedInstanceState Saved instance bundle from a previous instance
         */
        public void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
                @NonNull View v, @Nullable Bundle savedInstanceState) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onStart()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentStarted(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onResume()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onPause()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onStop()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentStopped(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onSaveInstanceState(Bundle)}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param outState Saved state bundle for the fragment
         */
        public void onFragmentSaveInstanceState(@NonNull FragmentManager fm, @NonNull Fragment f,
                @NonNull Bundle outState) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onDestroyView()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onDestroy()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onDetach()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) {}}Copy the code

So, if you consider a state-change listener registered by yourself in development, the method call process is shown below

Figure 2 and flow chart of FragmentLifecycleCallbacks fragments life cycle

1.2 Life cycle execution sequence source code analysis

1. Submit and display fragments

Normally we use the following code to submit and display a Fragment

XXXFragment fragment = new XXXFragment();
Bundle bundle = new Bundle();
bundle.putString("key"."value");
fragment.setAurguments(bundle);
/ / or commitAllowStateLoss
getSupportFragmentManager.beginTransaction().add(R.id.xxx, fragment).commit(); 
Copy the code

There are many ways to add a Fragment view, such as add(), replace()

With the add() method as an example, look at the code implementation

// FragmentTransaction.java ...... /** * Calls {@link #add(int, Fragment, String)} with a 0 containerViewId. */ @NonNull public FragmentTransaction add(@NonNull Fragment fragment, @Nullable String tag) { doAddOp(0, fragment, tag, OP_ADD); return this; }... 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."); } 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)); }... void addOp(Op op) { mOps.add(op); op.mEnterAnim = mEnterAnim; op.mExitAnim = mExitAnim; op.mPopEnterAnim = mPopEnterAnim; op.mPopExitAnim = mPopExitAnim; }Copy the code

It looks like a lot of code, but the main purpose is to save the OP_ADD instruction in the list, and then look at commit()

// BackStackRecord.java

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

int commitInternal(boolean allowStateLoss) {
	if (mCommitted) throw new IllegalStateException("commit already called");
	if (FragmentManagerImpl.DEBUG) {
		Log.v(TAG, "Commit: " + this);
		LogWriter logw = new LogWriter(TAG);
		PrintWriter pw = new PrintWriter(logw);
		dump("", pw);
		pw.close();	
  }
	mCommitted = true;
	if (mAddToBackStack) {
		mIndex = mManager.allocBackStackIndex(this);
	} else {
		mIndex = -1;
	}
  / / 1.
	mManager.enqueueAction(this, allowStateLoss);
	return mIndex;
}
Copy the code

① : Add the submit Fragment event to the execution queue for execution.

Digging deeper into the code, mManager is the FragmentManagerImpl object that executes its enqueueAction() method.

 		// FragmentManagerImpl.java

		/**
     * Adds an action to the queue of pending actions.
     *
     * @param action the action to add
     * @param allowStateLoss whether to allow loss of state information
     * @throws IllegalStateException if the activity has been destroyed
     */
    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<>();
            }
            / / 2.mPendingActions.add(action); scheduleCommit(); }}/**
     * 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();
          	/ / 3.
            booleanpendingReady = mPendingActions ! =null && mPendingActions.size() == 1;
            if (postponeReady || pendingReady) {
                mHost.getHandler().removeCallbacks(mExecCommit);
              	/ / 4.
                mHost.getHandler().post(mExecCommit);
                updateOnBackPressedCallbackEnabled();
            }
        }
    }

		Runnable mExecCommit = new Runnable() {
        @Override
        public void run(a) { execPendingActions(); }};/** * Only call from main thread! * /
    public boolean execPendingActions(a) {
        ensureExecReady(true);

        boolean didSomething = false;
        while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
            mExecutingActions = true;
            try {
                removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
            } finally {
                cleanupExec();
            }
            didSomething = true;
        }

        updateOnBackPressedCallbackEnabled();
        doPendingDeferredStart();
        burpActive();

        return didSomething;
    }

		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++;
                    }
                }
              	/ / 5.
                executeOpsTogether(records, isRecordPop, recordNum, reorderingEnd);
                startIndex = reorderingEnd;
                recordNum = reorderingEnd - 1; }}if (startIndex != numRecords) {
            executeOpsTogether(records, isRecordPop, startIndex, numRecords);
        }
    }

	private void executeOpsTogether(ArrayList<BackStackRecord> records,
                                    ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {...if(! allowReordering) { FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
                    false); } executeOps(records, isRecordPop, startIndex, endIndex); .for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
            final BackStackRecord record = records.get(recordNum);
            final boolean isPop = isRecordPop.get(recordNum);
            if (isPop && record.mIndex >= 0) {
                freeBackStackIndex(record.mIndex);
                record.mIndex = -1;
            }
            record.runOnCommitRunnables();
        }
        if(addToBackStack) { reportBackStackChanged(); }}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); record.executeOps(); }}}Copy the code

② : First initialize mPendingActions and add the event to the mPendingActions list. ScheduleCommit () is then executed.

③ : In the scheduleCommit method, mPendingActions are already initialized and have a size of 1. If the pendingReady flag bit is *true, perform step 4

④ Send an execution event to add a Fragment through the Handler, create a Fragment object, and execute various life-cycle methods.

⑤ : Execute the executeOps method, then execute record.executeops ()

// 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.mFragment;
            if(f ! =null) {
                f.setNextTransition(mTransition, mTransitionStyle);
            }
            switch (op.mCmd) {
                case OP_ADD:
                    f.setNextAnim(op.mEnterAnim);
                		/ / 6.
                    mManager.addFragment(f, false);
                    break;
                case OP_REMOVE:
                    f.setNextAnim(op.mExitAnim);
                    mManager.removeFragment(f);
                    break;
                case OP_HIDE:
                    f.setNextAnim(op.mExitAnim);
                    mManager.hideFragment(f);
                    break;
                case OP_SHOW:
                    f.setNextAnim(op.mEnterAnim);
                    mManager.showFragment(f);
                    break;
                case OP_DETACH:
                    f.setNextAnim(op.mExitAnim);
                    mManager.detachFragment(f);
                    break;
                case OP_ATTACH:
                    f.setNextAnim(op.mEnterAnim);
                    mManager.attachFragment(f);
                    break;
                case OP_SET_PRIMARY_NAV:
                    mManager.setPrimaryNavigationFragment(f);
                    break;
                case OP_UNSET_PRIMARY_NAV:
                    mManager.setPrimaryNavigationFragment(null);
                    break;
                case OP_SET_MAX_LIFECYCLE:
                    mManager.setMaxLifecycle(f, op.mCurrentMaxState);
                    break;
                default:
                    throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
            }
            if(! mReorderingAllowed && op.mCmd ! = OP_ADD && f ! =null) { mManager.moveFragmentToExpectedState(f); }}if(! mReorderingAllowed) {/ / all landowners
            // Added fragments are added at the end to comply with prior behavior.
            mManager.moveToState(mManager.mCurState, true); }}Copy the code

Add fragment (f, false) * MManager.addFragment (f, false) * MManager.addfragment (f, false) * MManager.addfragment (f, false)

// FragmentManagerImpl.java

public void addFragment(Fragment fragment, boolean moveToStateNow) {
        if (DEBUG) Log.v(TAG, "add: " + fragment);
        makeActive(fragment);
        if(! fragment.mDetached) {if (mAdded.contains(fragment)) {
                throw new IllegalStateException("Fragment already added: " + fragment);
            }
            synchronized (mAdded) {
                mAdded.add(fragment);
            }
            fragment.mAdded = true;
            fragment.mRemoving = false;
            if (fragment.mView == null) {
                fragment.mHiddenChanged = false;
            }
            if (isMenuAvailable(fragment)) {
                mNeedMenuInvalidate = true;
            }
            if(moveToStateNow) { moveToState(fragment); }}}Copy the code

⑦ : mmanager. moveToState Converts the current fragment state to the next new fragment state

/**
     * Changes the state of the fragment manager to {@code newState}. If the fragment manager
     * changes state or {@code always} is {@code true}, any fragments within it have their
     * states updated as well.
     *
     * @param newState The new state for the fragment manager
     * @param always If {@code true}, all fragments update their state, even
     *               if {@code newState} matches the current fragment manager's state.
     */
    void moveToState(int newState, boolean always) {
        if (mHost == null&& newState ! = Fragment.INITIALIZING) {throw new IllegalStateException("No activity");
        }

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

        mCurState = newState;

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

        // Now iterate through all active fragments. These will include those that are removed
        // and detached.
        for (Fragment f : mActive.values()) {
            if(f ! =null&& (f.mRemoving || f.mDetached) && ! f.mIsNewlyAdded) { moveFragmentToExpectedState(f); } } startPendingDeferredFragments();if(mNeedMenuInvalidate && mHost ! =null && mCurState == Fragment.RESUMED) {
            mHost.onSupportInvalidateOptionsMenu();
            mNeedMenuInvalidate = false; }}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); }}/ / end
        moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false); . }Copy the code

Today: this method is the Fragment is added to the display process, the life cycle method and registered FragmentLifecycleCallbacks to monitor specific executive position. The source code for this method is below

@SuppressWarnings("ReferenceEquality")
    void moveToState(Fragment f, int newState, int transit, int transitionStyle,
                     boolean keepActive) {
        // Fragments that are not currently added will sit in the onCreate() state.
        if((! f.mAdded || f.mDetached) && newState > Fragment.CREATED) { newState = Fragment.CREATED; }if (f.mRemoving && newState > f.mState) {
            if (f.mState == Fragment.INITIALIZING && f.isInBackStack()) {
                // Allow the fragment to be created so that it can be saved later.
                newState = Fragment.CREATED;
            } else {
                // While removing a fragment, we can't change it to a higher state.newState = f.mState; }}// Defer start if requested; don't allow it to move to STARTED or higher
        // if it's not already started.
        if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.ACTIVITY_CREATED) {
            newState = Fragment.ACTIVITY_CREATED;
        }
        // Don't allow the Fragment to go above its max lifecycle state
        // Ensure that Fragments are capped at CREATED instead of ACTIVITY_CREATED.
        if (f.mMaxState == Lifecycle.State.CREATED) {
            newState = Math.min(newState, Fragment.CREATED);
        } else {
            newState = Math.min(newState, f.mMaxState.ordinal());
        }
        if (f.mState <= newState) {
            // For fragments that are created from a layout, when restoring from
            // state we don't want to allow them to be created until they are
            // being reloaded from the layout.
            if(f.mFromLayout && ! f.mInLayout) {return;
            }
            if(f.getAnimatingAway() ! =null|| f.getAnimator() ! =null) {
                // The fragment is currently being animated... but! Now we
                // want to move our state back up. Give up on waiting for the
                // animation, move to whatever the final state should be once
                // the animation is done, and then we can proceed from there.
                f.setAnimatingAway(null);
                f.setAnimator(null);
                moveToState(f, f.getStateAfterAnimating(), 0.0.true);
            }
            switch (f.mState) {
                case Fragment.INITIALIZING:
                    if (newState > Fragment.INITIALIZING) {
                        if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
                        if(f.mSavedFragmentState ! =null) { f.mSavedFragmentState.setClassLoader(mHost.getContext() .getClassLoader()); f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG); Fragment target = getFragment(f.mSavedFragmentState, FragmentManagerImpl.TARGET_STATE_TAG); f.mTargetWho = target ! =null ? target.mWho : null;
                            if(f.mTargetWho ! =null) {
                                f.mTargetRequestCode = f.mSavedFragmentState.getInt(
                                        FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
                            }
                            if(f.mSavedUserVisibleHint ! =null) {
                                f.mUserVisibleHint = f.mSavedUserVisibleHint;
                                f.mSavedUserVisibleHint = null;
                            } else {
                                f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
                                        FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
                            }
                            if(! f.mUserVisibleHint) { f.mDeferStart =true;
                                if(newState > Fragment.ACTIVITY_CREATED) { newState = Fragment.ACTIVITY_CREATED; } } } 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.
                        if(f.mTarget ! =null) {
                            if(mActive.get(f.mTarget.mWho) ! = f.mTarget) {throw new IllegalStateException("Fragment " + f
                                        + " declared target fragment " + f.mTarget
                                        + " that does not belong to this FragmentManager!");
                            }
                            if (f.mTarget.mState < Fragment.CREATED) {
                                moveToState(f.mTarget, Fragment.CREATED, 0.0.true);
                            }
                            f.mTargetWho = f.mTarget.mWho;
                            f.mTarget = null;
                        }
                        if(f.mTargetWho ! =null) {
                            Fragment target = mActive.get(f.mTargetWho);
                            if (target == null) {
                                throw new IllegalStateException("Fragment " + f
                                        + " declared target fragment " + f.mTargetWho
                                        + " that does not belong to this FragmentManager!");
                            }
                            if (target.mState < Fragment.CREATED) {
                                moveToState(target, Fragment.CREATED, 0.0.true);
                            }
                        }

                        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
                case Fragment.CREATED:
                    // We want to unconditionally run this anytime we do a moveToState that
                    // moves the Fragment above INITIALIZING, including cases such as when
                    // we move from CREATED => CREATED as part of the case fall through above.
                    if (newState > Fragment.INITIALIZING) {
                        ensureInflatedFragmentView(f);
                    }

                    if (newState > Fragment.CREATED) {
                        if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
                        if(! f.mFromLayout) { ViewGroup container =null;
                            if(f.mContainerId ! =0) {
                                if (f.mContainerId == View.NO_ID) {
                                    throwException(new IllegalArgumentException(
                                            "Cannot create fragment "
                                                    + f
                                                    + " for a container view with no id"));
                                }
                                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;
                            f.performCreateView(f.performGetLayoutInflater(
                                    f.mSavedFragmentState), container, f.mSavedFragmentState);
                            if(f.mView ! =null) {
                                f.mInnerView = f.mView;
                                f.mView.setSaveFromParentEnabled(false);
                                if(container ! =null) {
                                    container.addView(f.mView);
                                }
                                if (f.mHidden) {
                                    f.mView.setVisibility(View.GONE);
                                }
                                f.onViewCreated(f.mView, f.mSavedFragmentState);
                                dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
                                        false);
                                // Only animate the view if it is visible. This is done after
                                // dispatchOnFragmentViewCreated in case visibility is changedf.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE) && f.mContainer ! =null;
                            } else {
                                f.mInnerView = null;
                            }
                        }

                        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) {
            switch (f.mState) {
                case Fragment.RESUMED:
                    if (newState < Fragment.RESUMED) {
                        if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
                        f.performPause();
                        dispatchOnFragmentPaused(f, false);
                    }
                    // fall through
                case Fragment.STARTED:
                    if (newState < Fragment.STARTED) {
                        if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
                        f.performStop();
                        dispatchOnFragmentStopped(f, false);
                    }
                    // fall through
                case Fragment.ACTIVITY_CREATED:
                    if (newState < Fragment.ACTIVITY_CREATED) {
                        if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
                        if(f.mView ! =null) {
                            // Need to save the current view state if not
                            // done already.
                            if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) {
                                saveFragmentViewState(f);
                            }
                        }
                        f.performDestroyView();
                        dispatchOnFragmentViewDestroyed(f, false);
                        if(f.mView ! =null&& f.mContainer ! =null) {
                            // Stop any current animations:
                            f.mContainer.endViewTransition(f.mView);
                            f.mView.clearAnimation();
                            AnimationOrAnimator anim = null;
                            // If parent is being removed, no need to handle child animations.
                            if (f.getParentFragment() == null| |! f.getParentFragment().mRemoving) {if(mCurState > Fragment.INITIALIZING && ! mDestroyed && f.mView.getVisibility() == View.VISIBLE && f.mPostponedAlpha >=0) {
                                    anim = loadAnimation(f, transit, false,
                                            transitionStyle);
                                }
                                f.mPostponedAlpha = 0;
                                if(anim ! =null) {
                                    animateRemoveFragment(f, anim, newState);
                                }
                                f.mContainer.removeView(f.mView);
                            }
                        }
                        f.mContainer = null;
                        f.mView = null;
                        // Set here to ensure that Observers are called after
                        // the Fragment's view is set to null
                        f.mViewLifecycleOwner = null;
                        f.mViewLifecycleOwnerLiveData.setValue(null);
                        f.mInnerView = null;
                        f.mInLayout = false;
                    }
                    // fall through
                case Fragment.CREATED:
                    if (newState < Fragment.CREATED) {
                        if (mDestroyed) {
                            // The fragment's containing activity is
                            // being destroyed, but this fragment is
                            // currently animating away. Stop the
                            // animation right now -- it is not needed,
                            // and we can't wait any more on destroying
                            // the fragment.
                            if(f.getAnimatingAway() ! =null) {
                                View v = f.getAnimatingAway();
                                f.setAnimatingAway(null);
                                v.clearAnimation();
                            } else if(f.getAnimator() ! =null) {
                                Animator animator = f.getAnimator();
                                f.setAnimator(null); animator.cancel(); }}if(f.getAnimatingAway() ! =null|| f.getAnimator() ! =null) {
                            // We are waiting for the fragment's view to finish
                            // animating away. Just make a note of the state
                            // the fragment now should move to once the animation
                            // is done.
                            f.setStateAfterAnimating(newState);
                            newState = Fragment.CREATED;
                        } else {
                            if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
                            booleanbeingRemoved = f.mRemoving && ! f.isInBackStack();if (beingRemoved || mNonConfig.shouldDestroy(f)) {
                                boolean shouldClear;
                                if (mHost instanceof ViewModelStoreOwner) {
                                    shouldClear = mNonConfig.isCleared();
                                } else if (mHost.getContext() instanceofActivity) { Activity activity = (Activity) mHost.getContext(); shouldClear = ! activity.isChangingConfigurations(); }else {
                                    shouldClear = true;
                                }
                                if (beingRemoved || shouldClear) {
                                    mNonConfig.clearNonConfigState(f);
                                }
                                f.performDestroy();
                                dispatchOnFragmentDestroyed(f, false);
                            } else {
                                f.mState = Fragment.INITIALIZING;
                            }

                            f.performDetach();
                            dispatchOnFragmentDetached(f, false);
                            if(! keepActive) {if (beingRemoved || mNonConfig.shouldDestroy(f)) {
                                    makeInactive(f);
                                } else {
                                    f.mHost = null;
                                    f.mParentFragment = null;
                                    f.mFragmentManager = null;
                                    if(f.mTargetWho ! =null) {
                                        Fragment target = mActive.get(f.mTargetWho);
                                        if(target ! =null && target.getRetainInstance()) {
                                            // Only keep references to other retained Fragments
                                            // to avoid developers accessing Fragments that
                                            // are never coming back
                                            f.mTarget = target;
                                        }
                                    }
                                }
                            }
                        }
                    }
            }
        }

        if(f.mState ! = newState) { Log.w(TAG,"moveToState: Fragment state for " + f + " not updated inline; "
                    + "expected state " + newState + " found "+ f.mState); f.mState = newState; }}Copy the code

The code is very long, but it is still easy to see. In INITIALIZING state, onFragmentPreAttach, onAttach, onFragmentAttched, onFragmentPreCreate, onCreate and so on were executed successively

In the CREATE state, onCreateView, onViewCreated, onActivityCreated and other methods are executed.

Second, the DialogFragment

2.1 DialogFragment Life Cycle

Check the inheritance relationship of DialogFragment. In fact, it inherits from a Fragment, so it has the characteristics and life cycle methods of the Fragment. DialogFragment also has some of the characteristics of a Dialog, which is reflected in the lifecycle method call flow. The onCreateDialog method is called before the onCreateView.

Figure 3 DialogFragment life cycle flow chart

Fragment execution onCreateView

					void moveToState(Fragment f, int newState, int transit, int transitionStyle,
                     boolean keepActive) {...switch (f.mState) {
                case Fragment.INITIALIZING:
                    		......
                    // fall through
                case Fragment.CREATED:
                    ......

                    if (newState > Fragment.CREATED) {
                        if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: "+ f); . f.mContainer = container;/ / attendingf.performCreateView(f.performGetLayoutInflater( f.mSavedFragmentState), container, f.mSavedFragmentState); . } 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; }}}Copy the code

⑩ : In performCreateView, you need to pass in LayoutInflater objects as arguments. LayoutInflater objects are obtained using the performGetLayoutInflater method. View the performGetLayoutInflater method.

LayoutInflater performGetLayoutInflater(@Nullable Bundle savedInstanceState) {
        LayoutInflater layoutInflater = onGetLayoutInflater(savedInstanceState);
        mLayoutInflater = layoutInflater;
        return mLayoutInflater;
    }
Copy the code

In the case of DialogFragment, it overrides the method and calls *onCreateDialog

@Override
    @NonNull
    public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
        if(! mShowsDialog) {return super.onGetLayoutInflater(savedInstanceState);
        }

        mDialog = onCreateDialog(savedInstanceState);

        if(mDialog ! =null) {
            setupDialog(mDialog, mStyle);

            return (LayoutInflater) mDialog.getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
        }
        return (LayoutInflater) mHost.getContext().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
    }
Copy the code

Third, BottomSheetDialogFragment

BottomSheetDialogFragment inherited from DialogFragment, that is to say, it also has some characteristics of DialogFragment (or more specifically, a Fragment of features). BottomSheetDialogFragment as at the bottom of the pop-up view, we will often and it BottomSheetBehavior used in combination, to complete the more complex interactions, or some other functions.

3.1 BottomSheetBehavior

BottomSheetBehavior is simpler to use. Create the BottomSheetBehavior object and bind it to the View that you want to associate with. Example code is as follows:

. mBehavior = BottomSheetBehavior.from((View) view.getParent()); mBehavior.addBottomSheetCallback(bottomSheetCallback); mBehavior.setPeekHeight(ScreenUtils.getScreenHeight(getContext())); .Copy the code

Note: The bottomsheetBehavior. from method requires that the LayoutParams of the passed View must be the LayoutParams of the CoordinatorLayout. But that doesn’t mean that the root layout of the View we create has to be CoordinatorLayout, it automatically gives us a layer, which is discussed below.

Through BottomSheetBehavior, we can add some slide and Fling to monitor events, or set BottomSheetDialogFragment open when the initial height, etc.

Question:

1) When the bottomsheetBehavior. from method is called, view.getparent () is passed in, because the root layout of the view is not *. If the view is called, * will raise an exception

java.lang.IllegalArgumentException: The view is not a child of CoordinatorLayout
        at com.google.android.material.bottomsheet.BottomSheetBehavior.from(BottomSheetBehavior.java:1634)
Copy the code

2) if the incoming the getParent () (i.e., the aforementioned BottomSheetDialogFragment will give us the view of the layout of the packet layer CoordinatorLayout), Creating the BottomSheetBehavior object in either the onCreateView or onViewCreated lifecycle method will get an error and prompt

java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.ViewGroup$LayoutParams android.view.View.getLayoutParams()' on a null object reference
        at com.google.android.material.bottomsheet.BottomSheetBehavior.from(BottomSheetBehavior.java:1632)
Copy the code

* view.getparent ()* has not been created yet. View.*getParent()* when to create view.

Screen:

BottomSheetDialogFragment is a subclass of DialogFragment, would in turn perform DialogFragment lifecycle methods, so screening is to look at what the method performs the logic in turn.

  • As shown in Figure 3, the BottomSheetDialog object is created in the onCreateDialog method, and the Dialog window object is created and the WindowManager object is set.

  • OnCreateView only creates a View object, onViewCreated is an empty method, and both methods look normal.

  • OnActivityCreated sets up the View to display and some listening events for the Dialog, as shown below

    		// DialogFragment.java
    
    		@Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
    
            if(! mShowsDialog) {return;
            }
    
            View view = getView();
            if(view ! =null) {
                if(view.getParent() ! =null) {
                    throw new IllegalStateException(
                            "DialogFragment can not be attached to a container view");
                }
              	/ / 1.
                mDialog.setContentView(view);
            }
            final Activity activity = getActivity();
            if(activity ! =null) {
                mDialog.setOwnerActivity(activity);
            }
            mDialog.setCancelable(mCancelable);
            mDialog.setOnCancelListener(this);
            mDialog.setOnDismissListener(this);
            if(savedInstanceState ! =null) {
                Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
                if(dialogState ! =null) { mDialog.onRestoreInstanceState(dialogState); }}}Copy the code

    (1) : See a Dialog under the setContentView method, because BottomSheetDialogFragment BottomSheetDialog object is created by onCreateDialog method, The BottomSheetDialog class overwrites the setContentView method, so look at the BottomSheetDialog#setContentView method source code

    	// BottomSheetDialog.java
    
     @Override
      public void setContentView(@LayoutRes int layoutResId) {
        super.setContentView(wrapInBottomSheet(layoutResId, null.null));
      }
    	
    	private View wrapInBottomSheet(
          int layoutResId, @Nullable View view, @Nullable ViewGroup.LayoutParams params) {
        ensureContainerAndBehavior();
        CoordinatorLayout coordinator = (CoordinatorLayout) container.findViewById(R.id.coordinator);
        if(layoutResId ! =0 && view == null) {
          view = getLayoutInflater().inflate(layoutResId, coordinator, false);
        }
    
        FrameLayout bottomSheet = (FrameLayout) container.findViewById(R.id.design_bottom_sheet);
        bottomSheet.removeAllViews();
        / / 2.
        if (params == null) {
          bottomSheet.addView(view);
        } else {
          bottomSheet.addView(view, params);
        }
        // We treat the CoordinatorLayout as outside the dialog though it is technically inside
        coordinator
            .findViewById(R.id.touch_outside)
            .setOnClickListener(
                new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                    if(cancelable && isShowing() && shouldWindowCloseOnTouchOutside()) { cancel(); }}});// Handle accessibility events
        ViewCompat.setAccessibilityDelegate(
            bottomSheet,
            new AccessibilityDelegateCompat() {
              @Override
              public void onInitializeAccessibilityNodeInfo(
                  View host, @NonNull AccessibilityNodeInfoCompat info) {
                super.onInitializeAccessibilityNodeInfo(host, info);
                if (cancelable) {
                  info.addAction(AccessibilityNodeInfoCompat.ACTION_DISMISS);
                  info.setDismissable(true);
                } else {
                  info.setDismissable(false); }}@Override
              public boolean performAccessibilityAction(View host, int action, Bundle args) {
                if (action == AccessibilityNodeInfoCompat.ACTION_DISMISS && cancelable) {
                  cancel();
                  return true;
                }
                return super.performAccessibilityAction(host, action, args); }}); bottomSheet.setOnTouchListener(new View.OnTouchListener() {
              @Override
              public boolean onTouch(View view, MotionEvent event) {
                // Consume the event and prevent it from falling through
                return true; }});return container;
      }
    Copy the code

    (2) : The important thing is the wrapInBottomSheet method, although we didn’t use CoordinatorLayout when we constructed our View, in this method, The system has a default layout file r.layout. design_bottom_sheet_dialog (see the code below), The method wrapInBottomSheet is to add our layout to the default layout (FrameLayout with id design_bottom_sheet), and the parent view of our layout is the CoordinatorLayout. So we don’t have to set up the CoordinatorLayout as the root layout.

    // R.layout.design_bottom_sheet_dialog
    
    
            
    <! -- ~ Copyright (C) 2015 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the  License. -->
    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">
    
        <android.support.design.widget.CoordinatorLayout
            android:id="@+id/coordinator"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true">
    
            <View
                android:id="@+id/touch_outside"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:importantForAccessibility="no"
                android:soundEffectsEnabled="false"
                tools:ignore="UnusedAttribute"/>
    
            <FrameLayout
                android:id="@+id/design_bottom_sheet"
                style="? attr/bottomSheetStyle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal|top"
                app:layout_behavior="@string/bottom_sheet_behavior"/>
    
        </android.support.design.widget.CoordinatorLayout>
    
    </FrameLayout>
    Copy the code

conclusion

  1. createBottomSheetBehaviorObject,If the View argument passed is not one ofCoordinatorLayoutThe immediate child View needs to be created after the onActivityCreated lifecycle method(e.g.,onActivityCreated,onStartEtc.).
  2. If the incomingViewThe argument is not someCoordinatorLayoutThe immediate child ofViewYou also need to set it in the layout filelayout_behaviorAttribute, otherwise “He View is not associated with BottomSheetBehavior” is displayed.