Navigation之详细聊聊Fragment的实现原理

1. 前言

Fragment真是一个非常老的家伙,它的第一条提交记录是在2010年,而最近的一条记录则是在2021年6月11号,足足11岁了,但是它却老当益壮,在Jetpack中大放异彩,Navigation组件就是基于Fragment的一个跳转组件,Google的单Activity项目结构就是一个Activity和多个Fragment项目结构。多年以来,一提到Fragment,大家脑海中的第一印象可能还停留在平板应用开发中了,它曾经在手机项目中高频使用Fragment的机会还真没那么多。一方面是因为手机项目一般都是多Activity结构实现的,不会涉及到那么多的Fragment使用,另一方面,Fragment也比Activity复杂,相比之下使用有点麻烦,甚至是痛苦,不好定位问题,所以在技术选型时,能避则避。它太难用了,以至于Square团队的Piwai大神(LeakCanary的作者)在2014年发表了一篇倡导反对使用Fragment的文章。不管怎么样,这么多年过去了,Android团队也一直没有放弃对Fragment的支持,甚至在Jetpack中,Fragment开始扮演越来越重要的角色了,最近关于Navigation的倡导使用越来越多了,加上我参与了一个平板项目,所以对Fragment主流程源码进行了一次全面的研究。收获颇多,相信将来对Navigation研究定是大有裨益。

2. 创建Fragment

根据官方文档,写一个简单的Demo。简单了解Fragment的使用。

2.1 新建一个Fragment类

class ExampleFragment extends Fragment {
    public ExampleFragment() {
        super(R.layout.example_fragment);
    }
}
复制代码

2.2 把Fragment添加到Activity上

把Fragment添加到Activity上可以通过xml文件和代码编程两种方式实现。本文只介绍第二种实现方式。

<!-- res/layout/example_activity.xml -->
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
复制代码
public class ExampleActivity extends AppCompatActivity {
    public ExampleActivity() {
        super(R.layout.example_activity);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                .setReorderingAllowed(true)
                .add(R.id.fragment_container_view, ExampleFragment.class, null)
                .commit();
        }
    }
}
复制代码

我们可以看到,主要是通过FragmentManager来将Fragment添加到fragment_container_view对应的布局中,Demo是蛮简单的,模板代码,网上拷贝一下就能用。但是FragmentManager、Transaction、commit()分别是什么意思,还有待深入探究一番。

3. 深入理解Fragment原理

3.1 Fragment本质是什么

3.1.1 Fragment本质是View

从前面的DEMO我们看到,Fragment会add到FragmentContainerView上面,能被添加到ViewGroup的组件,一定也是View或者ViewGroup。有源码为证:

//androidx.fragment.app.Fragment.java

ViewGroup mContainer;

// The View generated for this fragment.
View mView;
复制代码

那么既然如此,何必大费周章呢,直接用View来替代Fragment不就好了,addView()、removeView()使用起来更简单直观。

当然了Fragment本质是View但是又不仅限于此, 在真实的项目中,界面往往很复杂,业务逻辑也很复杂,往往需要处理各种UI状态变化,比如:增加一组View,替换一组View,删除一组View,还需要和Activity协作。总之,如果让开发者自己去实现这一套逻辑,恐怕比使用Fragment要麻烦的多吧。尽管Fragment看起来蛮复杂的,但是还是远比我们自己去实现相同功能简单的多。

3.1.2 Fragment本质不仅限于View

Fragment作为一个View可以被添加到Activity的布局中去。但是他更强大,还有以下几个特性:

  1. 处理生命周期,比Activity生命周期还要复杂
  2. 通过FragmentTransaction操作多个Fragment
  3. 通过FragmentManager维护一个回退栈,回退到上一个FragmentTransaction操作的界面
//androidx.fragment.app.Fragment.java

static final int INITIALIZING = -1;          // Not yet attached.
static final int ATTACHED = 0;               // Attached to the host.
static final int CREATED = 1;                // Created.
static final int VIEW_CREATED = 2;           // View Created.
static final int AWAITING_EXIT_EFFECTS = 3;  // Downward state, awaiting exit effects
static final int ACTIVITY_CREATED = 4;       // Fully created, not started.
static final int STARTED = 5;                // Created and started, not resumed.
static final int AWAITING_ENTER_EFFECTS = 6; // Upward state, awaiting enter effects
static final int RESUMED = 7;                // Created started and resumed.
复制代码

从代码我们可以看出,有8种状态。分为两类,View相关的以及和Activity状态相关的。后文会详细介绍,此处点到为止。

3.2 FragmentTransaction

3.2.1 FragmentTransaction概述

软件工程中有一门叫数据库的课程,数据库中有一个叫"事务"的概念,它表示多个操作是原子性的,要么一起成功,要么一起失败,典型的例子就是转账,张三给李四转账100元这么一个事务,存在两个操作,从张三的账户中扣除100元,给李四的账户增加100元。因为有了事务,不会存在只有某一个操作成功,而另一个操作失败的情况,否则后果不堪设想。

在Android中SharedPreference也有commit方法。

SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("name", “Tom”);
editor.putInt("age", 28);
editor.putBoolean("marrid",false);           
editor.commit();
复制代码

上述功能,一次提交做了三件事情,putString,putInt,putBoolean。

而FragmentTransaction和SharedPreference类似。它可以同时操作多个Fragment。

假设有布局文件如下:

<LinearLayout android:orientation="vertical">
    
    <ViewGroup1>
        <Fragment1 />
    </ViewGroup1>
    
    <ViewGroup2>
        <Fragment2 />
    </ViewGroup2>
    
    <ViewGroup3>
        <Fragment3 />
    </ViewGroup3>
    
</LinearLayout>
复制代码

假设我们想在ViewGroup1上增加Fragment1,ViewGroup2上用Fragment2替换掉,把ViewGroup3上的Fragment移除掉。

伪代码如下:

getSupportFragmentManager().beginTransaction()
    .setReorderingAllowed(true)
    .add(ViewGroup1, Fragment1.class, null)
    .replace(ViewGroup2,Fragment2.class,null)
    .remove(Fragment3)
    .commit();
复制代码

问:这三个操作我为什么要放到一个事务中呢?我连续执行这三个操作不行吗?

答:有些场景下可以,有些场景下不可以。涉及到回退栈的时候,是否放到事务中,按返回键会有很大的区别。我们把上述三个操作分别命名为OP1、OP2、OP3。如果调用了FragmentTransaction.addToBackStack(),三个操作放到事务中时, 按返回键时会执行与OP1、OP2、OP3相反的操作,即ViewGroup1、ViewGroup2、ViewGroup3同时恢复到处理事务之前的状态。如果三个操作不放到事务中时, 则会依次恢复 ViewGroup3、ViewGroup2、ViewGroup1的状态

3.2.2 FragmentTransaction源码分析

3.2.2.1 支持的操作OP_CMD

//FragmentTransaction.java
static final int OP_NULL = 0;
static final int OP_ADD = 1;
static final int OP_REPLACE = 2;
static final int OP_REMOVE = 3;
static final int OP_HIDE = 4;
static final int OP_SHOW = 5;
static final int OP_DETACH = 6;
static final int OP_ATTACH = 7;
static final int OP_SET_PRIMARY_NAV = 8;
static final int OP_UNSET_PRIMARY_NAV = 9;
static final int OP_SET_MAX_LIFECYCLE = 10;
复制代码

挑几个一眼就能看懂的。

  1. OP_ADD表示增加Fragment
  2. OP_REPLACE表示替换某个ViewGroup上的Fragment
  3. OP_REMOVE删除Fragment
  4. OP_HIDE隐藏Fragment,等同于View.setVisibility(View.GONE)
  5. OP_SHOW显示Fragment,等同于View.setVisibility(View.VISIBLE)
  6. OP_DETACH detach Fragment
  7. OP_ATTACH attach Fragment

对应的方法分别是:

//FragmentTransaction.java

public FragmentTransaction add(@NonNull Fragment fragment, @Nullable String tag)  {
    doAddOp(0, fragment, tag, OP_ADD);
    return this;
}

public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment) {
    return replace(containerViewId, fragment, null);
}

public FragmentTransaction remove(@NonNull Fragment fragment) {
    addOp(new Op(OP_REMOVE, fragment));

    return this;
}
复制代码

3.2.2.2 OP类

public abstract class FragmentTransaction {
    ArrayList<Op> mOps = new ArrayList<>();
    static final class Op {
          int mCmd;
          Fragment mFragment;
          int mEnterAnim;
          int mExitAnim;
          int mPopEnterAnim;
          int mPopExitAnim;
          Lifecycle.State mOldMaxState;
          Lifecycle.State mCurrentMaxState;

          Op() {
          }

          Op(int cmd, Fragment fragment) {
              this.mCmd = cmd;
              this.mFragment = fragment;
              this.mOldMaxState = Lifecycle.State.RESUMED;
              this.mCurrentMaxState = Lifecycle.State.RESUMED;
          }

          Op(int cmd, @NonNull Fragment fragment, Lifecycle.State state) {
              this.mCmd = cmd;
              this.mFragment = fragment;
              this.mOldMaxState = fragment.mMaxState;
              this.mCurrentMaxState = state;
          }
      }
}
复制代码

从源码中我们可以看到,FragmentTransaction中有ArrayList<Op> mOps。它表示一个事务中需要处理几个操作。而OP类最重要的两个字段就是mCmd和mFragment。表示对某个Fragment执行指定的操作。

3.2.2.3 FragmentTransaction.commit()方法

//FragmentTransaction.java
public abstract class FragmentTransaction {
    public abstract int commit();
}
复制代码

我们看到commit()方法是个抽象方法。它由BackStackRecord类实现。

final class BackStackRecord extends FragmentTransaction implements
      FragmentManager.BackStackEntry, FragmentManager.OpGenerator {
  @Override
  public int commit() {
      return commitInternal(false);
  }
}
复制代码

3.3 BackStackRecord

3.3.1 BackStackRecord概述

BackStackRecord是FragmentTransaction的子类,表示它支持同时操作多个Fragment。同时,顾名思义,它会被放到BackStack(回退栈)中。回退栈定义在FragmentManager中,是一个ArrayList集合。

//FragmentManager.java
ArrayList<BackStackRecord> mBackStack;
复制代码

一言以蔽之,BackStackRecord支持事务操作,同时又会被放入到回退栈中

3.3.2 BackStackRecord commit流程

int commitInternal(boolean allowStateLoss) {
    if (mCommitted) throw new IllegalStateException("commit already called");
    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
        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();
    } else {
        mIndex = -1;
    }
    mManager.enqueueAction(this, allowStateLoss);
    return mIndex;
}
复制代码

该方法最主要的就是调用了FragmentManager.enqueueAction方法

3.4 FragmentManager

3.4.1 FragmentManager概述

从前文,我们知道,对多个Fragment的操作会被记录到FragmentTransaction中,最终调用FragmentManager.enqueueAction方法,真正执行Fragment操作。

3.4.2 操作调用流程

1. FragmentManager.enqueueAction()

void enqueueAction(@NonNull OpGenerator action, boolean allowStateLoss) {
    if (!allowStateLoss) {
        if (mHost == null) {
            if (mDestroyed) {
                throw new IllegalStateException("FragmentManager has been destroyed");
            } else {
                throw new IllegalStateException("FragmentManager has not been attached to a "
                        + "host.");
            }
        }
        checkStateLoss();
    }
    synchronized (mPendingActions) {
        if (mHost == null) {
            if (allowStateLoss) {
                // This FragmentManager isn't attached, so drop the entire transaction.
                return;
            }
            throw new IllegalStateException("Activity has been destroyed");
        }
        mPendingActions.add(action);
        scheduleCommit();
    }
}
复制代码

该方法最终调用到scheduleCommit()方法

2. FragmentManager.scheduleCommit()

void scheduleCommit() {
    synchronized (mPendingActions) {
        boolean postponeReady =
                mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
        boolean pendingReady = mPendingActions.size() == 1;
        if (postponeReady || pendingReady) {
            mHost.getHandler().removeCallbacks(mExecCommit);
            mHost.getHandler().post(mExecCommit);
            updateOnBackPressedCallbackEnabled();
        }
    }
}
复制代码

该方法最终通过Handler执行mExecCommit

3. FragmentManager.mExecCommit

private Runnable mExecCommit = new Runnable() {
    @Override
    public void run() {
        execPendingActions(true);
    }
};
复制代码

该方法最终调用execPendingActions

4. FragmentManager.execPendingActions()

boolean execPendingActions(boolean allowStateLoss) {
    ensureExecReady(allowStateLoss);

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

    updateOnBackPressedCallbackEnabled();
    doPendingDeferredStart();
    mFragmentStore.burpActive();

    return didSomething;
}
复制代码

重点关注removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop)

5. FragmentManager.removeRedundantOperationsAndExecute()

private void removeRedundantOperationsAndExecute(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isRecordPop) {
    
}
复制代码

方法比较长省略代码,最终调用executeOpsTogether

6. FragmentManager.executeOpsTogether

private void executeOpsTogether(@NonNull ArrayList<BackStackRecord> records,
          @NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
}
复制代码

方法比较长省略代码,最终调用executeOps方法

7. FragmentManager.executeOps()

private static void executeOps(@NonNull ArrayList<BackStackRecord> records,
            @NonNull 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();
        }
    }
}
复制代码

该方法分为两种情况,入栈和出栈,对应commit()和FragmentManager.popBackStack()操作。

8. FragmentManager.executeOps()

void executeOps() {
        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.setPopDirection(false);
                f.setNextTransition(mTransition);
                f.setSharedElementNames(mSharedElementSourceNames, mSharedElementTargetNames);
            }
            switch (op.mCmd) {
                case OP_ADD:
                    f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                    mManager.setExitAnimationOrder(f, false);
                    mManager.addFragment(f);
                    break;
                case OP_REMOVE:
                    f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                    mManager.removeFragment(f);
                    break;
                case OP_HIDE:
                    f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                    mManager.hideFragment(f);
                    break;
                case OP_SHOW:
                    f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                    mManager.setExitAnimationOrder(f, false);
                    mManager.showFragment(f);
                    break;
                case OP_DETACH:
                    f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                    mManager.detachFragment(f);
                    break;
                case OP_ATTACH:
                    f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                    mManager.setExitAnimationOrder(f, false);
                    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) {
                if (!FragmentManager.USE_STATE_MANAGER) {
                    mManager.moveFragmentToExpectedState(f);
                }
            }
        }
        if (!mReorderingAllowed && !FragmentManager.USE_STATE_MANAGER) {
            // Added fragments are added at the end to comply with prior behavior.
            mManager.moveToState(mManager.mCurState, true);
        }
    }
复制代码

该方法做了两件事,其一:根据OP.mCmd,操作FragmentManager对应的方法,该步骤并不会真正执行操作,也只是做记录操作,其二:调用mManager.moveToState(mManager.mCurState, true)

最终会调用到mManager.moveToState(Fragment f, int newState)方法,它是Fragment框架中真正核心方法

3.5 FragmentManager的核心方法void moveToState(Fragment f, int newState)

作为核心方法,并没有放在3.4章节中,只是为了突出它的核心地位。

void moveToState(@NonNull Fragment f, int newState) {
    FragmentStateManager fragmentStateManager = mFragmentStore.getFragmentStateManager(f.mWho);
    if (fragmentStateManager == null) {
        // Ideally, we only call moveToState() on active Fragments. However,
        // in restoreSaveState() we can call moveToState() on retained Fragments
        // just to clean them up without them ever being added to mActive.
        // For these cases, a brand new FragmentStateManager is enough.
        fragmentStateManager = new FragmentStateManager(mLifecycleCallbacksDispatcher,
                mFragmentStore, f);
        // Only allow this FragmentStateManager to go up to CREATED at the most
        fragmentStateManager.setFragmentManagerState(Fragment.CREATED);
    }
    // When inflating an Activity view with a resource instead of using setContentView(), and
    // that resource adds a fragment using the <fragment> tag (i.e. from layout and in layout),
    // the fragment will move to the VIEW_CREATED state before the fragment manager
    // moves to CREATED. So when moving the fragment manager moves to CREATED and the
    // inflated fragment is already in VIEW_CREATED we need to move new state up from CREATED
    // to VIEW_CREATED. This avoids accidentally moving the fragment back down to CREATED
    // which would immediately destroy the Fragment's view. We rely on computeExpectedState()
    // to pull the state back down if needed.
    if (f.mFromLayout && f.mInLayout && f.mState == Fragment.VIEW_CREATED) {
        newState = Math.max(newState, Fragment.VIEW_CREATED);
    }
    newState = Math.min(newState, fragmentStateManager.computeExpectedState());
    if (f.mState <= newState) {
        // If we are moving to the same state, we do not need to give up on the animation.
        if (f.mState < newState && !mExitAnimationCancellationSignals.isEmpty()) {
            // The fragment is currently being animated...  but!  Now we
            // want to move our state back up.  Give up on waiting for the
            // animation and proceed from where we are.
            cancelExitAnimation(f);
        }
        switch (f.mState) {
            case Fragment.INITIALIZING:
                if (newState > Fragment.INITIALIZING) {
                    fragmentStateManager.attach();
                }
                // fall through
            case Fragment.ATTACHED:
                if (newState > Fragment.ATTACHED) {
                    fragmentStateManager.create();
                }
                // 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) {
                    fragmentStateManager.ensureInflatedView();
                }

                if (newState > Fragment.CREATED) {
                    fragmentStateManager.createView();
                }
                // fall through
            case Fragment.VIEW_CREATED:
                if (newState > Fragment.VIEW_CREATED) {
                    fragmentStateManager.activityCreated();
                }
                // fall through
            case Fragment.ACTIVITY_CREATED:
                if (newState > Fragment.ACTIVITY_CREATED) {
                    fragmentStateManager.start();
                }
                // fall through
            case Fragment.STARTED:
                if (newState > Fragment.STARTED) {
                    fragmentStateManager.resume();
                }
        }
    } else if (f.mState > newState) {
        switch (f.mState) {
            case Fragment.RESUMED:
                if (newState < Fragment.RESUMED) {
                    fragmentStateManager.pause();
                }
                // fall through
            case Fragment.STARTED:
                if (newState < Fragment.STARTED) {
                    fragmentStateManager.stop();
                }
                // fall through
            case Fragment.ACTIVITY_CREATED:
                if (newState < Fragment.ACTIVITY_CREATED) {
                    if (isLoggingEnabled(Log.DEBUG)) {
                        Log.d(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) {
                            fragmentStateManager.saveViewState();
                        }
                    }
                }
                // fall through
            case Fragment.VIEW_CREATED:
                if (newState < Fragment.VIEW_CREATED) {
                    FragmentAnim.AnimationOrAnimator anim = null;
                    if (f.mView != null && f.mContainer != null) {
                        // Stop any current animations:
                        f.mContainer.endViewTransition(f.mView);
                        f.mView.clearAnimation();
                        // If parent is being removed, no need to handle child animations.
                        if (!f.isRemovingParent()) {
                            if (mCurState > Fragment.INITIALIZING && !mDestroyed
                                    && f.mView.getVisibility() == View.VISIBLE
                                    && f.mPostponedAlpha >= 0) {
                                anim = FragmentAnim.loadAnimation(mHost.getContext(),
                                        f, false, f.getPopDirection());
                            }
                            f.mPostponedAlpha = 0;
                            // Robolectric tests do not post the animation like a real device
                            // so we should keep up with the container and view in case the
                            // fragment view is destroyed before we can remove it.
                            ViewGroup container = f.mContainer;
                            View view = f.mView;
                            if (anim != null) {
                                FragmentAnim.animateRemoveFragment(f, anim,
                                        mFragmentTransitionCallback);
                            }
                            container.removeView(view);
                            if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
                                Log.v(FragmentManager.TAG, "Removing view " + view + " for "
                                        + "fragment " + f + " from container " + container);
                            }
                            // If the local container is different from the fragment
                            // container, that means onAnimationEnd was called, onDestroyView
                            // was dispatched and the fragment was already moved to state, so
                            // we should early return here instead of attempting to move to
                            // state again.
                            if (container != f.mContainer) {
                                return;
                            }
                        }
                    }
                    // If a fragment has an exit animation (or transition), do not destroy
                    // its view immediately and set the state after animating
                    if (mExitAnimationCancellationSignals.get(f) == null) {
                        fragmentStateManager.destroyFragmentView();
                    }
                }
                // fall through
            case Fragment.CREATED:
                if (newState < Fragment.CREATED) {
                    if (mExitAnimationCancellationSignals.get(f) != null) {
                        // We are waiting for the fragment's view to finish animating away.
                        newState = Fragment.CREATED;
                    } else {
                        fragmentStateManager.destroy();
                    }
                }
                // fall through
            case Fragment.ATTACHED:
                if (newState < Fragment.ATTACHED) {
                    fragmentStateManager.detach();
                }
        }
    }

    if (f.mState != newState) {
        if (isLoggingEnabled(Log.DEBUG)) {
            Log.d(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
                    + "expected state " + newState + " found " + f.mState);
        }
        f.mState = newState;
    }
}
复制代码

该方法是整个Fragment框架中的核心方法,它会根据目标state和Fragment当前的state一步一步的升级或降级Fragment的State。最终回调到Fragment的相关生命周期方法。 至此整个commit方法的调用链条就分析完毕了。

由于篇幅有限,mManager.moveToState(Fragment f, int newState)我将新写一篇文章,专门图解一番。其实LifeCycle组件的State变化也是类似的,一步一步升级或降级


 

热门文章

暂无图片
编程学习 ·

C语言二分查找详解

二分查找是一种知名度很高的查找算法&#xff0c;在对有序数列进行查找时效率远高于传统的顺序查找。 下面这张动图对比了二者的效率差距。 二分查找的基本思想就是通过把目标数和当前数列的中间数进行比较&#xff0c;从而确定目标数是在中间数的左边还是右边&#xff0c;将查…
暂无图片
编程学习 ·

GMX 命令分类列表

建模和计算操作命令&#xff1a; 1.1 . 创建拓扑与坐标文件 gmx editconf - 编辑模拟盒子以及写入子组(subgroups) gmx protonate - 结构质子化 gmx x2top - 根据坐标生成原始拓扑文件 gmx solvate - 体系溶剂化 gmx insert-molecules - 将分子插入已有空位 gmx genconf - 增加…
暂无图片
编程学习 ·

一文高效回顾研究生课程《数值分析》重点

数值分析这门课的本质就是用离散的已知点去估计整体&#xff0c;就是由黑盒子产生的结果去估计这个黑盒子。在数学里这个黑盒子就是一个函数嘛&#xff0c;这门课会介绍许多方法去利用离散点最大化地逼近这个函数&#xff0c;甚至它的导数、积分&#xff0c;甚至微分方程的解。…
暂无图片
编程学习 ·

在职阿里5年,一个28岁女软测工程师的心声

简单的先说一下&#xff0c;坐标杭州&#xff0c;14届本科毕业&#xff0c;算上年前在阿里巴巴的面试&#xff0c;一共有面试了有6家公司&#xff08;因为不想请假&#xff0c;因此只是每个晚上去其他公司面试&#xff0c;所以面试的公司比较少&#xff09; ​ 编辑切换为居中…
暂无图片
编程学习 ·

字符串左旋c语言

目录 题目&#xff1a; 解题思路&#xff1a; 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 总代码&#xff1a; 题目&#xff1a; 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符…
暂无图片
编程学习 ·

设计模式--观察者模式笔记

模式的定义与特点 观察者&#xff08;Observer&#xff09;模式的定义&#xff1a;指多个对象间存在一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式&#xf…
暂无图片
编程学习 ·

睡觉突然身体动不了,什么是睡眠痽痪症

很多朋友可能有这样的体验&#xff0c;睡觉过程中突然意识清醒&#xff0c;身体却动弹不了。这时候感觉非常恐怖&#xff0c;希望旁边有一个人推自己一下。阳光以前也经常会碰到这样的情况&#xff0c;一年有一百多次&#xff0c;那时候很害怕晚上到来&#xff0c;睡觉了就会出…
暂无图片
编程学习 ·

深入理解C++智能指针——浅析MSVC源码

文章目录unique_ptrshared_ptr 与 weak_ptrstd::bad_weak_ptr 异常std::enable_shared_from_thisunique_ptr unique_ptr 是一个只移型别&#xff08;move-only type&#xff0c;只移型别还有std::mutex等&#xff09;。 结合一下工厂模式&#xff0c;看看其基本用法&#xff…
暂无图片
编程学习 ·

@TableField(exist = false)

TableField(exist false) //申明此字段不在数据库存在&#xff0c;但代码中需要用到它&#xff0c;通知Mybatis-plus在做写库操作是忽略它。,.
暂无图片
编程学习 ·

Java Web day15

第十二章文件上传和下载 一、如何实现文件上传 要实现Web开发中的文件上传功能&#xff0c;通常需要完成两步操作&#xff1a;一.是在Web页面中添加上传输入项&#xff1b;二是在Servlet中读取上传文件的数据&#xff0c;并保存到本地硬盘中。 需要使用一个Apache组织提供一个…
暂无图片
编程学习 ·

【51nod 2478】【单调栈】【前缀和】小b接水

小b接水题目解题思路Code51nod 2478 小b接水 题目 输入样例 12 0 1 0 2 1 0 1 3 2 1 2 1输出样例 6解题思路 可以发现最后能拦住水的都是向两边递减高度&#xff08;&#xff1f;&#xff09; 不管两个高积木之间的的积木是怎样乱七八糟的高度&#xff0c;最后能用来装水的…
暂无图片
编程学习 ·

花了大半天写了一个UVC扩展单元调试工具

基于DIRECTSHOW 实现的&#xff0c;用的是MFC VS2019. 详见&#xff1a;http://www.usbzh.com/article/detail-761.html 获取方法 加QQ群:952873936&#xff0c;然后在群文件\USB调试工具&测试软件\UVCXU-V1.0(UVC扩展单元调试工具-USB中文网官方版).exe USB中文网 USB中文…
暂无图片
编程学习 ·

贪心(一):区间问题、Huffman树

区间问题 例题一&#xff1a;区间选点 给定 N 个闭区间 [ai,bi]请你在数轴上选择尽量少的点&#xff0c;使得每个区间内至少包含一个选出的点。 输出选择的点的最小数量。 位于区间端点上的点也算作区间内。 输入格式 第一行包含整数 N&#xff0c;表示区间数。 接下来 …
暂无图片
编程学习 ·

C语言练习实例——费氏数列

目录 题目 解法 输出结果 题目 Fibonacci为1200年代的欧洲数学家&#xff0c;在他的着作中曾经提到&#xff1a;「若有一只免子每个月生一只小免子&#xff0c;一个月后小免子也开始生产。起初只有一只免子&#xff0c;一个月后就有两只免子&#xff0c;二个月后有三只免子…
暂无图片
编程学习 ·

Android开发(2): Android 资源

个人笔记整理 Android 资源 Android中的资源&#xff0c;一般分为两类&#xff1a; 系统内置资源&#xff1a;Android SDK中所提供的已经定义好的资源&#xff0c;用户可以直接拿来使用。 用户自定义资源&#xff1a;用户自己定义或引入的&#xff0c;只适用于当前应用的资源…
暂无图片
编程学习 ·

零基础如何在短时间内拿到算法offer

​算法工程师是利用算法处理事物的职业 算法&#xff08;Algorithm&#xff09;是一系列解决问题的清晰指令&#xff0c;也就是说&#xff0c;能够对一定规范的输入&#xff0c;在有限时间内获得所要求的输出。 如果一个算法有缺陷&#xff0c;或不适合于某个问题&#xff0c;执…
暂无图片
编程学习 ·

人工智能:知识图谱实战总结

人工智能python&#xff0c;NLP&#xff0c;知识图谱&#xff0c;机器学习&#xff0c;深度学习人工智能&#xff1a;知识图谱实战前言一、实体建模工具Protegepython&#xff0c;NLP&#xff0c;知识图谱&#xff0c;机器学习&#xff0c;深度学习 人工智能&#xff1a;知识图…
暂无图片
编程学习 ·

【无标题】

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…