【Fragment】对Fragment、FragmentManager和BackStackRecord的字段全解析

Fragment、FragmentManager以及BackStackRecord是Fragment组件包中最主要的三个类,几乎包含了Fragment体系中所有的功能和知识点。从主流使用流程出发的源码分析对解决问题快速有效,但是一段时间后其中细节点可能就会慢慢的遗忘,只剩下一个感性的认知。本篇从字段入手,对这三个类的所有字段从含义、作用、使用方式以及所涉及的额外知识点几个方面的进行了详细的分析记录,有利于对源码分析和回顾,甚至完全可以当作一本字段参考字典。

起因

在决定尝试对Fragment系列进行源码分析后,尽管很努力地想按功能模块将Fragment的各个特性独立成篇,但仍有很多知识点存在强烈的关联性,不得不在每篇文章中重复地提及,既然如此,干脆就将这些知识点细分并分别分析记录,于是逐渐地转变为对整个类的所有字段进行分析。

经过大半月的业余时间的学习和分析,从中发现了许多不曾了解的知识点、纠正了许多错误的想法、增强了对已有结论的认识,一段有趣又漫长的工作。

注:

  • 本文源码基于support-fragment-25.0.0版本。
  • 篇幅较长建议在PC端打开,点击右下角菜单按钮可展开目录,便于索引。
  • 如有遗漏或错误,欢迎指正和建议。

Fragment

mAnimatingAway && mStateAfterAnimating

mAnimatingAway:表示当前正在执行动画的view,一般这个view持有的就是Fragment的mView。若mAnimatingAway不为null就表示当前fragment's view hierarchy正在执行动画中,一旦动画结束,mAnimatingAway就会被置null。

mStateAfterAnimating:若mAnimatingAway!=null,即当前fragment’s view hierarchy正在执行动画中,那么mStateAfterAnimating就表示在动画结束后Fragment应该move to的状态state

源码注释中没提到的是,它们都是针对退出Fragment时的动画而言,其实从命名也可以猜出一二。

首先,mAnimatingAway只在一处被赋值,它就是5个参数的moveToState中,调用f.performDestroyView()的case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// FragmentManager # moveToState(...)
case Fragment.ACTIVITY_CREATED:
...
// performDestroyView将Fragment的当前状态mState置为了 CREATED;
f.performDestroyView();
if (f.mView != null && f.mContainer != null) {
Animation anim = null;
if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
// loadAnimation根据fragment.mNextAnim,transit和transitionStyle三个条件返回一个anim
anim = loadAnimation(f, transit, false, transitionStyle);
}
if (anim != null) {
final Fragment fragment = f;
// anim不为null表示指定了动画,所以mAnimatingAway被赋值为了Fragment的布局文件
f.mAnimatingAway = f.mView;
// 并将Fragment需要move to的新状态保存在mStateAfterAnimating中
f.mStateAfterAnimating = newState;
final View viewToAnimate = f.mView;
// 对动画设置监听
anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener(viewToAnimate, anim) {
@Override public void onAnimationEnd(Animation animation) {
super.onAnimationEnd(animation);
// 当动画结束,才开始继续进行下一步的状态更新操作,并清空mAnimatingAway
// 移动的新状态就是动画开始前临时保存在mStateAfterAnimating的状态
if (fragment.mAnimatingAway != null) {
fragment.mAnimatingAway = null;
moveToState(fragment, fragment.mStateAfterAnimating, 0, 0, false);
}
}
});
// 然后对整个布局文件开始动画
f.mView.startAnimation(anim);
}
// 不论是否有动画,Fragment所属视图在performDestroyView()后就立即被移除了
f.mContainer.removeView(f.mView);
}
...

Fragment的退出动画是在onDestroyView之后开始执行的,而视图是立刻被移除的。并且由于Fragment的最新状态是动画结束时才开始切换的,所以只要动画没结束,Fragment的当前状态就只能moveto到CREATED,不会切换为最新状态。
由于case之间没有break,为了防止动画还未结束,Fragment就执行完了onDestroy、onDettach(若需要),在调用f.performDestroy()的case中进行了判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// FragmentManager # moveToState(...)
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
if (mDestroyed) {
// 如果动画期间宿主Activity需要destroy了,那么就不应该还要等到Fragment的动画完成的才结束
// 而是直接cancel掉(不会回调onAnimationEnd),并清除mAnimatingAway标识以正常状态流程切换。
if (f.mAnimatingAway != null) {
View v = f.mAnimatingAway;
f.mAnimatingAway = null;
v.clearAnimation();
}
}
// 表示正在执行退出动画,只需等待,
// 同时更新f.mStateAfterAnimating为最新状态以让动画结束时Fragment能同步至最新状态
if (f.mAnimatingAway != null) {
f.mStateAfterAnimating = newState;
newState = Fragment.CREATED;
} else {// 否则根据正常流程条件依次执行destroy、dettach
if (!f.mRetaining) {
f.performDestroy();
} else {
f.mState = Fragment.INITIALIZING;
}
f.performDetach();
...
}
}

如果在destroyView后正在执行退出动画期间,Fragment又有了新的状态切换(“活”过来了),例如重新被attach了或者重新createView了,那么在切换为最新状态前,动画会被直接忽视掉并直接先把状态切换到动画结束时应该切换到的状态,以走完上次未走完的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) {
...
if (f.mState < newState) {
...
if (f.mAnimatingAway != null) {
f.mAnimatingAway = null;
moveToState(f, f.mStateAfterAnimating, 0, 0, true);
}
switch (f.mState) {
...
}
}
...
}

mIndex

mIndex是Fragment的唯一凭证,通过mIndex可以找到对应的Fragment,只要与FragmentManager存在关联便会存在;否则在Fragment执行onDestroy/onDetach后,FragmentManager会调用makeInactive方法将mIndex从Fragment释放并回收到mAvailIndices集合。

结合FragmentManager中的mActive和mAvailIndices字段一起看,值是当前Fragment在mActive数组的索引。

调用关系链:
mIndex <- Fragment#setIndex <- FragmentManager#makeActive <- FragmentManager#addFragment / BackStackRecord#setLastIn

被add的Fragment在FragmentManager调用addFragment方法时,会为Fragment分配唯一索引ID,由makeActive执行具体执行过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void makeActive(Fragment f) {
// 重新从mActive集合中add到宿主上的Fragment或者经历状态保持与恢复机制的Fragment已经存在ID,无需再分配
if (f.mIndex >= 0) {
return;
}
// 如果没有空余的ID,则取mActive集合的最后元素的ID再加1,以保持ID的连续性
if (mAvailIndices == null || mAvailIndices.size() <= 0) {
if (mActive == null) {
mActive = new ArrayList<Fragment>();
}
f.setIndex(mActive.size(), mParent);
mActive.add(f);
} else {// 否则从可用ID集合中取出最后一个作为该Fragment的ID,并添加到mActive集合。
f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
mActive.set(f.mIndex, f);
}
}

mWho

内部字段,可标识Fragment的唯一性;与mIndex的赋值时期相同。
当在Fragment中启动Activity/IntentSender/请求权限时,用于确定事件的来源Fragment,以便在宿主Activity的结果回调dispatchActivityResult时根据该字段查找相应的Fragment进行结果转发。查找规则如下:

1
// Activity --> mFragmentManager: for --> Fragment#findFragmentByWho

生成规则如下:

1
2
3
4
5
6
7
8
final void setIndex(int index, Fragment parent) {
mIndex = index;
if (parent != null) {
mWho = parent.mWho + ":" + mIndex;
} else {
mWho = "android:fragment:" + mIndex;
}
}

其作用范围限制于FragmentManager内,即Activity不会把结果分发其它Activity内的Fragment。

mArguments

可以简单的将其作为Fragment的构造参数来理解。但是与构造参数不同的是,mArguments可以在状态保存恢复机制中得到持久化,意味着Fragment恢复时,该参数依然有效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 在saveAllState时保存了mArguments
public FragmentState(Fragment frag) {
...
mArguments = frag.mArguments;
...
}
// Fragment
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
...
Fragment f = (Fragment)clazz.newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
// 在恢复Fragment时,构造完Fragment后第一件事就是应用mArguments
f.mArguments = args;
}
...
}

可以通过setArguments(Bundle)为Fragment传递参数,但是有一点需要注意的是,该方法调用时必须保证当前Fragment还未Attach到Activity,也即Fragment还未关联至FragmentManager,Fragment的mIndex仍为-1。

mTarget && mTargetIndex && mTargetRequestCode

mTarget是一个Fragment对象,mTargetIndex是mTarget的index。

与Fragment之间的数据传递有关。如果Fragment A想启动Fragment B完成一些操作,并希望B在结束前返回一个result给A,这有点像Activity之间的forResult启动方式,那么就可以通过对B设置setTargetFragment(A, requestCode)来实现。例如:

1
2
3
4
5
6
7
8
9
10
11
12
public class Caller extends Fragment {
...
Fragment called = Called.newInstance();
called.setTargetFragment(this, 99);
called.show(...);
...
}
public class Called extends DialogFragment {
...
getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_OK, intent);
...
}

虽然官方对于Fragment之间的communication推荐通过以宿主Activity为桥梁的方式来完成,但是mTarget更加适合与Activity无关、任务型启动等情况。

状态保存与恢复机制中,当前Fragment会持久化mTargetd的Index和RequestCode来记录自己的target。

mAdded

指示当前Fragment是否已被add至FragmentManager,即存在于FragmentManager中的mAdded数组中;

初始状态下为false,当状态恢复时也会先被置为初始状态;

当add/remove时被置为true/false,当attach/dettach时若发现未add/已被add则置为true/false,同时mAdded的变化会同步到FragmentManager中的mAdded数组上。

mRemoving

指示当前Fragment正在从Activity中被removing;仅当执行remove时,该状态会被置为true,在remove完成,状态改变后,就会被重置为初始状态false。

mFromLayout

指示当前Fragment是通过静态加载的方式实例化的。主要用来区别静态加载和动态创建两种实例化Fragment的方式。

mInLayout

拦截器作用。

当静态加载一个Fragment时,其mInLayout会被置为true。同时在状态保存恢复机制中,该字段没有被持久化,默认的false。所以如果Fragment是之前静态加载的,即mFromLayout为true,那么恢复状态后继续moveToState时(下面给出解释),该过程就会因为mInLayout为false而被阻止直到setContentView的调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void moveToState(5 params) {
...
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;
...
}
...
}
...
}

为什么需要阻止?

首先我们需要知道的是,在Activity#onCreate方法的super.onCreate中与Fragment相关的操作主要有两步,分别是恢复Fragment、分发onCreate事件对Fragment进行moveState:

1
2
3
4
5
6
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
mFragments.restoreAllState(...);
mFragments.dispatchCreate();
...
}

第一步中不管是静态加载还是动态创建的Fragment都会恢复;而第二步中最后move State调用的方法moveToState(5参)在关于Fragment生命周期方法的回调时机方面,静态加载和动态创建两种方式表现是不同的:
如果是静态加载的Fragment,其onCreateView、onViewCreated两个方法是在INITIAL move to CREATED时发生的,而动态创建的Fragment相应的方法是在CREATED move to other(>created)时发生的。

也就是说,在分发onCreatd事件进行状态转移至CREATED时,会导致静态加载的Fragment执行与视图加载相关的方法onCreateView、onViewCreated,而动态创建的Fragment不会。而关键是此时宿主Activity还未执行setContentView也就是视图还并不存在,如果在这时候让其子Fragment拥有了视图,并执行与视图相关的操作,导致的异常情况第一是视图可能的修改过程不可见,第二是可能出现与视图相关的Crash。所以就需要在宿主Activity加载视图前,阻止子Fragment进行自身的视图加载。同时,这样的处理也符合静态加载由宿主布局加载触发的规则。

mRestored

指示当前Fragment是从之前保存的状态中恢复过来的;在FragmentState#instantiate中被显式地置为了true;

当Fragment通过动态加载方式实例化时,在回调Fragment#onCreateView生命周期前会先查找Fragment所要依附的containerView,即onCreateView的第二个参数,此时就会根据mRestored来判断是否是全新的加载流程(即不是从保存的状态恢复过来的),来检查其是否为null并抛出异常:

1
2
3
4
5
6
7
8
container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
...
throwException(new IllegalArgumentException("No view found for id 0x" ... );
}
f.mContainer = container;
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), container, f.mSavedFragmentState);
...

mBackStackNesting

“回退栈”中包含有该Fragment的事务总数。也就是说,包含了对该Fragment的操作的事务如果全部未保存(addToBackStack)或都已被出栈,该参数就是0。

  • 可以通过此参数来判断Fragment是否被关联到了当前栈中:Fragment#isInBackStack()。该判定作用于remove操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void removeFragment(...) {
    final boolean inactive = !fragment.isInBackStack();
    // 虽然Fragment已被dettach,但是有可能还关联在回退栈中。
    if (!fragment.mDetached || inactive) {
    if (mAdded != null) {
    mAdded.remove(fragment);
    }
    ...
    }
    }
  • 当某个事务被“保存”到了回退栈,那么每当该事务被执行run时,该事务中所关联的所有Fragment(包括OP_REPLACE中的op.removed)的mBackStackNesting就会被+1,同时在执行Op操作OP_REPLACE时,需要被remove的Fragment的mBackStackNesting还会被额外+1,保证了mBackStackNesting表示关联事务数含义的正确性。
    每当该事务被出栈popFromBackStack时,相应的mBackStackNesting就会被-1。这些都是通过bumpBackStackNesting方法来操作的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // BackStackRecord
    void bumpBackStackNesting(int amt) {
    // mAddToBackStack表示当前事务是否已被添加进了回退栈
    if (!mAddToBackStack) {
    return;
    }
    Op op = mHead;
    // 将该事务所关联的所有Fragment的mBackStackNesting都执行了+/-
    while (op != null) {
    if (op.fragment != null) {
    op.fragment.mBackStackNesting += amt;
    }
    if (op.removed != null) {
    for (int i=op.removed.size()-1; i>=0; i--) {
    Fragment r = op.removed.get(i);
    r.mBackStackNesting += amt;
    }
    }
    op = op.next;
    }
    }
  • 虽然该字段在状态保存与恢复期间并不会被持久化,但是由于FragmentManager保存了回退栈,所以在恢复状态时,mBackStackNesting会被重新计算恢复为状态丢失前的值。

mFragmentManager

1
2
3
// The fragment manager we are associated with.  Set as soon as the
// fragment is used in a transaction; cleared after it has been removed
// from all transactions.

mHost

1
// Host this fragment is attached to.

mChildFragmentManager

1
// Private fragment manager for child fragments inside of this one.

mChildNonConfig

1
2
// For use when restoring fragment state and descendant fragments are retained.
// This state is set by FragmentState.instantiate and cleared in onCreate.

mParentFragment

1
// If this Fragment is contained in another Fragment, this is that container.

提供了一个和Parent通信的方式。

mFragmentId

一个可选的唯一性标识,静态加载时为<fragment>的id属性,可以使用FragmentManager#findFragmentById找到对应的Fragment实例缓存;动态加载时则和container ID相同,即所依附的视图容器的ID。

注意:不要试图在动态加载的方式下对已经add到cntainerView上的Fragment使用findFragmentById方式查找,因为使用事务提交时,默认约束了Fragment的mFragmentId等于container ID

在使用事务add/replace时,会对Fragment的mFragmentId及其所依附的containerView的Id进行检查,因为默认情况下被add的Fragment的mFragmentId最后被指定为了mContainerView的Id,所以在commit时发现ragment.mFragmentId != containerViewId就会抛出异常阻止操作,防止已被add的Fragment再被add至其它containerView:

1
2
3
4
5
6
7
8
9
10
11
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
...
if (containerViewId != 0) {
...
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment " ...);
}
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
...
}

mContainerId

如果Fragment是使用动态加载的方式添加的,那么该字段标识的就是其所依附的containerView的ID,在构建事务时指定(具体是在doAddOp方法中)。

如果Fragment是静态加载的,该字段就是<fragment/>标签的parent的id(若存在);但是若<fragment/>标签没有为Fragment指定id或者tag,并且其parent也正好没有id,就会抛出异常终止静态创建。(因为在没有为Fragment指定id或者tag时,FragmentManager会尝试以parent id去查找Fragment缓存)。

该字段存在的意义是用来找到Fragment的mView的parent容器,仅对动态加载方式有效。而动态加载下找到mView的parent是为了什么,具体参见Fragment.mContainer字段。

mTag

一个可选的标识,通常用来查找对应的Fragment缓存实例:

1
2
3
4
5
6
7
8
9
10
// 通过FragmentManager查找:
public Fragment findFragmentByTag(String tag) {
if (mAdded != null && tag != null) {
...
}
if (mActive != null && tag != null) {
...
}
return null;
}

若一个Fragment实例被设置了mTag,那么不能再对其指定其它mTag:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在添加事务时:
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
...
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;
}
...
}

mHidden

Fragment的显示/隐藏状态,实则是对Fragment的mView进行VISIBLE/GONE;默认状态为false,即视图显示;与mHidden相关的方法是onHiddenChanged,只要mHidden发生改变,那么onHiddenChanged就会回调,mHidden发生改变是通过显式调用show/hide命令来完成的,除此之外,mHidden是不会变化的(除dettach后被重置外):

1
2
3
4
5
6
7
8
9
10
11
public void showFragment(Fragment fragment, int transition, int transitionStyle) {
if (fragment.mHidden) {
fragment.mHidden = false;
if (fragment.mView != null) {
...
fragment.mView.setVisibility(View.VISIBLE);
}
...
fragment.onHiddenChanged(false);
}
}

可以发现,若状态未发生变化,方法是不会发生任何变化的。

mDetached

标识Fragment是否已被Dettach,通过显式调用attach/dettach命令来改变状态。如果Fragment已被dettach,那么add/remove命令是无效的:

1
2
3
4
5
6
7
8
// FragmentManager
public void addFragment(Fragment fragment, boolean moveToStateNow) {
...
makeActive(fragment);
if (!fragment.mDetached) {
...
}
}

mRetainInstance && mRetaining

mRetainInstance 指示当前Fragment需要保持实例状态,即不随宿主Activity的销毁重建而销毁重建。可以在Fragment中通过setRetainInstance(true)来开启该特性。

mRetaining 指示当前Fragment正处于宿主Activity的销毁和重建的过程中,即保持实例中ing;或者解释为标识当前Fragment是一个保持了实例未被销毁的Fragment,所以在moveToState中需要对其进行特殊对待。

应用场景:有时候在设备配置发生变化时Activity需要立即销毁并重建;而有些自定义变量需要保持 或 处理过程不需要因此中断,例如线程,就可以使用该方式来维持Fragment实例以满足需求。++注意与Activity被kill的区别,不论是主动finish还是因内存不足而killed++。

原理:若不需要自行处理Activity配置的变更(即在Manifest中未对Activity进行android:configChanges配置,所以称为NonConfig),Activity会销毁并重建。系统在销毁前会回调Activity#onRetainNonConfigurationInstance(),可以用来保留一些可以传递到新Activity的实例,在FragmentActivity中该方法被重写:

1
2
3
4
5
6
7
8
9
10
11
12
// FragmentActivity
@Override
public final Object onRetainNonConfigurationInstance() {
...
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
...
NonConfigurationInstances nci = new NonConfigurationInstances();
...
nci.fragments = fragments;
...
return nci;
}

mFragments.retainNestedNonConfig()最终调用了FragmentManager#retainNonConfig方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// FragmentManager
FragmentManagerNonConfig retainNonConfig() {
ArrayList<Fragment> fragments = null;
ArrayList<FragmentManagerNonConfig> childFragments = null;
...
for (int i=0; i<mActive.size(); i++) {
...
// 重要标识:仅当Fragment调用了setRetainInstance(true),该实例才会被缓存在fragments中
if (f.mRetainInstance) {
if (fragments == null) {
fragments = new ArrayList<Fragment>();
}
fragments.add(f);
// 开启该标识,以防止在moveToState时的部分生命周期方法的调用
f.mRetaining = true;
...
}
...
// 当然,其嵌套子Fragments如果也开启了mRetainInstance,也会被保存
if (f.mChildFragmentManager != null) {
FragmentManagerNonConfig child = f.mChildFragmentManager.retainNonConfig();
...
childFragments.add(child);
...
}
...
}
// 如果该FragmentManager下没有任何Fragment需要保持实例,则返回null
if (fragments == null && childFragments == null) {
return null;
}
// 最后,将缓存包装在FragmentManagerNonConfig对象中返回
return new FragmentManagerNonConfig(fragments, childFragments);
}

纵观整个方法的流程,其实就是将需要保持实例的Fragment及其相应的嵌套子Fragment实例都保存在了FragmentManagerNonConfig对象中并返回,FragmentActivity将得到的该对象又被包装在NonConfigurationInstances里返回给了系统;我们知道,在Activity被重新create时,Activity里首次被调用的方法是attach(…):

1
2
3
4
5
final void attach(...NonConfigurationInstances lastNonConfigurationInstances...) {
...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
...
}

通过该方法系统传入了上次包装实例的缓存对象lastNonConfigurationInstances,然后在经历生命周期过程第一个方法onCreate时,Activity直接使用了该对象来恢复Fragments实例(针对.app.Fragment),在FragmentActivity中也是一样(针对.app.v4.Fragment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Activity#onCreate
...
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.fragments : null);
}
----------------------------------------------------------------------------------------------
// FragmentActivity#onCreate
...
// getLastNonConfigurationInstance方法取就是传入Activity中的mLastNonConfigurationInstances对象
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
...
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
...
}

更多可以参见Fragment的状态保存与恢复机制一文。

mHasMenu && mMenuVisible

注意:Fragment里与这两个字段相关的Menu指的都是OptionsMenu,注意与其它菜单类型的区别。
OptionsMenu指的是通过物理menu按钮点击显示的菜单或者是ActionBar/ToolBar上显示的actions。

  • mHasMenu表示当前Fragment有可被展示的OptionsMenu。默认为FALSE,可以通过Fragment的setHasOptionsMenu(true)方法开启(具体开启方法见 附:OptionMenu的使用方法,下同),将当前Activity下的Menu切换为Fragment的Menu。
  • mMenuVisible表示在当前Fragment存在OptionsMenu的前提下(即建立在mHasMenu的基础之上),该Menu是否可见,默认为TRUE,可以通过setMenuVisibility(boolean)关闭。例如我们add了一个Fragment,但是该Fragment目前被同容器下的另一个Fragment遮挡而看不见,那么就可以将该字段设置为FALSE,以同步正确的状态。

    注意:这里的“看不见”不包括显示状态设置为不可见(即HIDDEN)。源码中与OptionsMenu相关的三个回调方法的前置条件是mHidden为FALSE。

只有当两个字段状态同时满足的前提下,OptionMenu才有效。

并且,Fragment的任何事务操作,例如 add/show/… 等都会依据这两个字段修改FragmentManager的mNeedMenuInvalidate属性(置为True),在所有的Fragment进行movetToState整体状态切换完成后,会依据mNeedMenuInvalidate来触发invalidateOptionsMenu(Activity)方法开始OptionsMenu流程,因此可以说Menu的显示/移除等都是“实时”的。

原理

两个字段其实最终触发的仍然是宿主Activity的invalidateOptionsMenu(Activity)方法,所以本质上与Activity本身的OptionsMenu使用流程相同:
invalidateOptionsMenu()会导致宿主Activity重新初始化OptionsMenu流程,该流程会依次调用宿主Activity的onCreateOptionsMenu(Menu)onPrepareOptionsMenu(Menu)。而我们开启Fragment的mHasMenu后,这两个方法被回调的同时,Fragment的对应的同名方法也会被回调,因此只要在Fragment的onCreateOptionsMenu(Menu)中setup自己的Menu就可以将OptionMenu替换为Fragment自己的Menu。
当然,在其它情况例如Fragment被Hide时,虽然仍旧会触发Activity的的onCreateOptionsMenu(Menu)onPrepareOptionsMenu(Menu),但是Fragment的对应方法不会被回调,取而代之的是回调方法onDestroyOptionsMenu()(在存在optionMenu的去前提下),这是因为FragmentManager在分发对应的optionMenu事件时会让Fragment检查自己的Hidden状态,如果是hide,则FragmentManager在通知完onDestroyOptionsMenu()后,返回给宿主是false,使得Fragment对应的方法无法回调。具体可以参见FragmentManager的mCreatedMenus字段详解。

++另外,源码中mHasMenu与mMenuVisible总是成对被调用,因此这两个字段的根本意义是相同的,不同的字段名是为了更直观地区分控制 “拥有” / “显示” 两个状态。++

附:OptionMenu的使用方法

使用OptionMenu一共需要重写4个方法,除了onOptionsMenuClosed方法未找到任何方式触发回调、不需要再关心外,剩余的3个方法分别是 onCreateOptionsMenu、onPrepareOptionsMenu、onOptionsItemSelected 。而Menu可以通过xml配置或者动态构建。

  1. onCreateOptionsMenu

OptionsMenu初始化流程的第一步,默认在Activity启动时会触发该流程,在第一步中该流程会依次回调Activity和Fragment的对应方法,当然,Fragment被回调的前提是Fragment开启了mHasMenu和mMenuVisible。回调Fragment的顺序是按照Fragment被添加的顺序来的,下同,不再重复分析。
另外,Activity中的该方法需要返回一个boolean变量,若返回false不会进行到下一步的onPrepareOptionsMenu流程。

  1. onPrepareOptionsMenu

OptionsMenu初始化流程的第二步,除此之外,每次点击菜单按钮也会触发该步骤的回调。该步骤照例会同onCreateOptionsMenu一样依次回调Activity和Fragment对应的方法。
并且Activity中的该方法也需要返回一个boolean值,如果返回false,这些actions就不会出现。

  • onOptionsItemSelected

这是actions的事件回调方法。所有的actions事件 不管系统是否处理,最后都会来到该方法。如果 Activity 包含有Fragment,则系统将依次为 Activity 和每个Fragment(按照每个Fragment的添加顺序)调用 onOptionsItemSelected(),直到有一个返回结果为 true 或所有Fragment均调用完毕为止。

另外,actions并非就完全指OptionsMenu的item,还包括ActionBar/ToolBar上面的其它系统提供的可选按钮,例如”Up Button” 按钮,一个左上角的返回按钮:

1
2
3
4
5
6
7
8
9
10
/**
* 1. "Up Button" 表示可以回到所指定的Activity,取代默认的回到栈中上一个页面的逻辑,这个Activity称作parent;<br/>
* 2. parent需要在Manifest中为当前Activity通过android:parentActivityName指定;<br/>
* 3. 原理:其实就是Activity的“出栈”,通过该按钮可以直接destroy掉栈中parent与当前Activity之间的元素,并显示parent;<br/>
* 4. 若指定的parent并不存在于Activity栈中或者存在但已经destroy了,则按钮的事件和普通的finish无异;<br/>
* 5. 若并没有指定parent,那么按钮除了照例回调onOptionsItemSelected外,没有任何事件响应;<br/>
* 6. 默认launchMode下,parent若还“alive”,会先被destroy再create(注意:与状态保存恢复机制的重建不同);<br/>
* 7. 若不需要parent先被destroy,可以将launchMode指定为singleTop;<br/>
*/
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

mNextAnim

表示与Fragment关联的接下来会使用的anim动画,注意与构建事务时指定的动画的联系与区别。

联系

mNextAnim的取值全部来自构建事务时指定的动画(若未重写fragment.onCreateAnimation),具体可以参见BackStackRecord的Op字段详解或BackStackRecord分析一文。

区别

区别在于,构建事务时指定的动画是包含Fragment在几种时机所需执行的动画的集合,例如Fragment被add/show/hide/detach等;而mNextAnim就是在前面所说的特定时机到来时被赋值为指定的动画集合中与该时机所关联的某一个动画,所以称为接下来会使用的anim动画。

mContainer

1
// The parent container of the fragment after dynamically added to UI.

也就是说,对于静态加载的Fragment,该字段为null,以下分析不适用于该方式

对于动态加载的Fragment,mContainer在FragmentManager触发执行onCreateView之前通过containerId得到,containerId的具体细节参见对应的字段详解。

在构建事务时如果指定了containerId(传入0表示不指定),但该ID不属于当前宿主视图中的任何一个,在这一步创建mContainer时便会抛出异常终止。

当mContainer创建完成后(不管是否为null)便会开始回调onCreateView方法得到Fragment的视图mView(在moveToState方法升序的CREATED分支下),在mContainer不为null的前提下,mView被addView到了mContainer:

1
2
3
4
5
6
7
8
9
10
11
f.mContainer = container;
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
...
if (container != null) {
...
container.addView(f.mView);
}
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
}

从上述片段中同时还可以知道:在动态方式下如果未对Fragment指定container,即mContainer为null,表示该Fragment是一个无视图的对象,因此不管该Fragment的onCreateView是否返回一个具体的mView,都已不重要。

最后在moveToState方法降序的ACTIVITY_CREATED分支下,也就是回调onDestroyView后onDestroy前,该字段将Fragment的视图remove了并把自己置为了null:

1
2
3
4
5
6
7
8
...
f.performDestroyView();
if (f.mView != null && f.mContainer != null) {
...
f.mContainer.removeView(f.mView);
}
f.mContainer = null;
...

结论

所以最后的结论是:该字段用作容纳动态创建的Fragment的mView,在回调onCreateView方法前被创建,在回调onCreateView方法后立即将mView添加进来,在onDestroyView后立即将mView移除。

mView

表示Fragment的视图(若存在),在moveToState中由FragmentManager的performCreateView触发调用Fragment的onCreateView返回,在ondestroyView后被置null,在Fragment对象中的任何地方可以通过getView()快速取得。
我们调用最多的操作之一show/hide Fragment实际调用的就是对字段的setVisibility方法。

mInnerView

该字段等同于mView,仅用作Fragment视图的状态保存与恢复枢纽。

既然mInnerView与mView相同,为什么不直接使用mView反而额外提供一个mInnerView?

先给出原因:为了分离宿主Activity和Fragment的在视图层面上的状态保存恢复。

我们知道,在宿主Activity触发状态保存时,会对对应的视图层级内的所有View进行状态保存,其中就包括add到宿主的Fragment的mView,但是对于Fragment,其mView仅是属于自身状态的一部分,Fragment自身整体状态才属于宿主Activity状态的一部分,因此如果让Fragment的mView随宿主一起进行状态保存,那么当宿主率先恢复状态时,由于Fragment的mView的创建是要滞后于宿主的,恢复的Fragment的mView还未创建,那么属于Fragment的mView的状态就无法得到恢复而造成状态丢失。因此mInnerView的存在就是为了将宿主Activity与Fragment的状态保存分离,进行分别保存。

分离的第一步就是在Fragment创建视图时:

1
2
3
4
5
6
7
8
9
10
11
12
13
f.mView = f.performCreateView(f.getLayoutInflater(...);
if (f.mView != null) {
f.mInnerView = f.mView;
if (Build.VERSION.SDK_INT >= 11) {
ViewCompat.setSaveFromParentEnabled(f.mView, false);
} else {
f.mView = NoSaveStateFrameLayout.wrap(f.mView);
}
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
} else {
f.mInnerView = null;
}

  • setSaveFromParentEnabled:表示对传入的View参数,关闭其层级开始及其以下所有的子View的保存状态不再受其parent的控制;
  • NoSaveStateFrameLayout.wrap: 由于setSaveFromParentEnabled方法是API>=11开始新增的,在此之前只有layout才有该方法,所以这里的NoSaveStateFrameLayout是对mView进行一层包裹,以兼容处理。

也就是说,依靠ViewCompat.setSaveFromParentEnabled(f.mView, false);NoSaveStateFrameLayout.wrap(f.mView);,Fragment彻底切断了其mView与宿主Activity之间状态保存的层级调用关系。

接着由于Fragment的mView层级下的状态保存调用关系链已被切断,因此在切断之前提供了mInnerView来备份mView。当Fragment的状态保存与恢复机制被触发时,mView层级下的所有View的状态保存便是由mInnerView来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 调用链:saveAllState -> saveFragmentBasicState -> saveFragmentViewState
void saveFragmentViewState(Fragment f) {
...
f.mInnerView.saveHierarchyState(mStateArray);
if (mStateArray.size() > 0) {
f.mSavedViewState = mStateArray;
mStateArray = null;
}
}
// 视图创建后,mInnerView就代表mView
final void restoreViewState(Bundle savedInstanceState) {
if (mSavedViewState != null) {
mInnerView.restoreHierarchyState(mSavedViewState);
mSavedViewState = null;
}
...
}

mDeferStart && mUserVisibleHint

可以理解为延迟开始的标识,如果当前Fragment的状态还没有走到Fragment.STARTED,那么当mDeferStart为true时,Fragment的状态最多只能到Fragment.STOPPED,无法通过moveToState继续切换到Fragment.STARTED:

1
2
3
4
5
6
7
8
// FragmentManager
moveToState(Fragment, int, int, int, boolean) {
...
if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
newState = Fragment.STOPPED;
}
...
}

mDeferStart具体是由setUserVisibleHint(boolean)控制的,setUserVisibleHint是Fragment提供的一个 可见提示 状态UserVisibleHint(注意与Hidden是没有关系的,即使hidden为false,UserVisibleHint也不一定为true)的手动切换方法,默认状态是可见的,例如某些情况Fragment是SHOW状态,但是布局并未显示在屏幕当中,我们可以手动将它的UserVisibleHint置为false,Fragment会根据该状态对生命周期状态做出一些特定调整,这样就可以方便我们根据该特性来决定是否执行一些操作。

1
2
3
4
5
6
7
8
9
10
11
12
// 如果UserVisibleHint状态未被改变(即默认本身就是可见,我们无需再次设置可见),则该方法不影响Fragment的生命周期状态。
public void setUserVisibleHint(boolean isVisibleToUser) {
// 在Fragment被add且状态未到STARTED前的前提下,
// 当Fragment之前被标为不可见,现在切换为可见时,流程进入该分支,开始继续生命周期状态的切换流程。
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
&& mFragmentManager != null && isAdded()) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
// 也就是说,如果Fragment已经STARTED了,则该方法就不起作用了。
mDeferStart = mState < STARTED && !isVisibleToUser;
}

当开启这一特性使Fragment的生命周期状态阻塞到STOPPED后利用它完成自定义操作时,还需要恢复Fragment的生命周期状态到宿主Activity的当前生命周期所对对应的状态,这里一共有3种触发方式:

  1. 再次手动调用setUserVisibleHint改变状态,一般是false开启,true恢复。
  2. 如果有loader运行,loader加载完成/销毁时是一个触发点。
  3. 事务执行完成时、所有事务的全部执行完成时。

方式1:调用setUserVisibleHint

当符合条件,setUserVisibleHint会直接关闭mDeferStart标识并调用moveToState开始切换Fragment的状态到当前Activity所对应的状态。在决定继续生命周期状态的切换之前,performPendingDeferredStart还会再检查一下事务:如果当前正在执行Fragment事务transactions,就跳过并给个状态标识mHavePendingDeferredStart,以示等待:

1
2
3
4
5
6
7
8
9
10
11
12
// FragmentManager
public void performPendingDeferredStart(Fragment f) {
if (f.mDeferStart) {
if (mExecutingActions) {
// Wait until we're done executing our pending transactions
mHavePendingDeferredStart = true;
return;
}
f.mDeferStart = false;
moveToState(f, mCurState, 0, 0, false);
}
}

mHavePendingDeferredStart标识表示有一个DeferredStart还没执行,需要在mPendingActions事务结束后执行它。

Fragment的事务是在execPendingActions()方法(以commit方式提交)或者execSingleAction方法(以commiyNow方式提交)中执行的,在这两个方法的最后,都调用了方法doPendingDeferredStart:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// FragmentManager
void doPendingDeferredStart() {
if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
for (int i = 0; i < mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null && f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
if (!loadersRunning) {
mHavePendingDeferredStart = false;
startPendingDeferredFragments();
}
}
}

该方法中根据mHavePendingDeferredStart标识来判断是否还有DeferredStart在等待执行,同时DeferredStart可以被执行的另一个前提是FragmentManager中任何一个Fragment当前都没有loader正在执行,若两个条件都满足了则依次通过调用方法startPendingDeferredFragments()performPendingDeferredStart(f)对FragmentManager中的Fragment进行遍历执行,最终流程执行回了performPendingDeferredStart方法,开始moveState

1
2
3
4
5
6
7
8
// FragmentManager
void startPendingDeferredFragments() {
...
for (...) {
...
performPendingDeferredStart(f);
}
}

也就是说,当手动调用setUserVisibleHint请求为Fragment恢复为最新状态时,需要保证当前的事务(若正在执行)和loader(若正在执行)都执行完毕,才会触发最终的moveState。

方式2:loader未开始 或 加载完成/销毁时

当然,若当前存在正在运行的loader,不管有多少个,只要在最后一个loader执行完毕或者销毁时,也会调用performPendingDeferredStart根据deferredStart触发moveToState执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// LoaderManager
@Override
public void destroyLoader(int id) {
...
if (mHost != null && !hasRunningLoaders()) {
mHost.mFragmentManager.startPendingDeferredFragments();
}
}
@Override
public void onLoadComplete(Loader<Object> loader, Object data) {
...
if (mHost != null && !hasRunningLoaders()) {
mHost.mFragmentManager.startPendingDeferredFragments();
}
}

方式3:相关事务的执行完毕时

除了一个Looper周期内整体的mPendingActions执行完成时会根据mHavePendingDeferredStart标识来触发moveState外,单个事务执行完成时也会触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BackStackRecord
@Override
public void run() {
...
mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);
...
}
// FragmentManager
void moveToState(int newState, int transit, int transitStyle, boolean always) {
...
if (!loadersRunning) {
startPendingDeferredFragments();
}
...
}

方式4:Fragment pop返回栈时

Fragment出栈调用的是FragmentManager#popBackStack()或其重载方法,它们内部经过一系列调用链后最终执行到了d单个事务的具体出栈操作BackStackRecord#popFromBackStack(...)方法,当此次出栈的所有事务都完成出栈后,也调用了4个参数的moveToState方法:

1
2
3
4
5
6
7
8
9
10
11
// BackStackRecord
public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
...
if (doStateMove) {
mManager.moveToState(mManager.mCurState,
FragmentManagerImpl.reverseTransit(transition), transitionStyle, true);
...
}
...
}

结论:不管时哪一种方式,哪一个时机,只要存在mDeferStart标识,最后就会触发5个参数的moveState方法,将Fragment的生命周期状态f.mState恢复到与宿主Actitity最新状态对应的状态mCurState

xxTransitionxx

FragmentManager

mPendingActions

本字段可以结合BackStackRecord一文来分析。

pending有“等待、挂起”的意思,mPendingActions表示等待即将执行的动作的集合。这些动作包括提交给FragmentManager的事务以及FragmentManager执行出栈的操作:

1
2
3
4
5
6
7
8
9
10
11
12
// 提交事务
int commitInternal(boolean allowStateLoss) {
...
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
// 事务出栈
public void popBackStackXXX() {
enqueueAction(new Runnable() {
@Override public void run() {popBackStackState(...);}
}, boolean);
}

所有只要是不需要立即执行的 事务 / 出栈,都会先通过方法enqueueAction(Runnable, allowStateLoss)被加入到该集合中,并等待主线程Handler的回调执行。

原理

  1. 向mPendingActions集合添加动作时,一旦集合中有了数据,就会向主线程的Handler post一个mExecCommit的Runnable,该动作会在Handler下个回调周期执行mPendingActions里面的 等待动作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public void enqueueAction(Runnable action, boolean allowStateLoss) {
    ...
    if (mPendingActions == null) {
    mPendingActions = new ArrayList<Runnable>();
    }
    mPendingActions.add(action);
    if (mPendingActions.size() == 1) {
    mHost.getHandler().removeCallbacks(mExecCommit);
    mHost.getHandler().post(mExecCommit);
    }
    }

    mExecCommit = new Runnable() {
    @Override public void run() {execPendingActions();}
    };

    注:在Handler下个回调周期前,enqueueAction方法可能被调用多次,这样mPendingActions就存在多个 等待动作 的可能性。

  2. 一旦Handler开始回调,execPendingActions()方法就开始执行,该方法在满足前提的条件下,会将mPendingActions集合中的 等待动作 依次取出来遍历执行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public boolean execPendingActions() {
    ...
    while (true) {
    ...
    if (mPendingActions == null || mPendingActions.size() == 0) {
    break;
    }
    ...
    // 取出放入mTmpActions数组
    mPendingActions.toArray(mTmpActions);
    ...
    // 遍历numActions执行Runnable
    for (int i=0; i<numActions; i++) {
    mTmpActions[i].run();
    mTmpActions[i] = null;
    }
    ...
    }
    ...
    }

存在的意义

这其实是为了效率。我们不可避免的会在同一时间提交多个事务,而事务的执行有相当部分是存在重复性的操作的,如果立即执行,对资源的消耗是可观的,为了避免这种情况,FragmentManager提供了mPendingActions字段来结合Handler实现该方案。
而对于出栈操作,执行的正是事务的“反操作”,因此也可以理解为一种事务。

mTmpActions

一个临时的动作数组集合,在mPendingActions的执行时机到来时(即execPendingActions方法执行时),为了++避免在执行mPendingActions里面的 等待动作 期间又需要 提交事务/出栈 而需要加锁引起的同步低效率++,这里引出mTmpActions数组来专门盛放从mPendingActions取出的动作以供执行,降低了对mPendingActions的占用。

具体可以参见mPendingActions的字段详解。

mExecutingActions

“正在执行动作” 的标识。具体讲就是只要有 事务/出栈 开始执行,该变量就会被置为TRUE,一旦完成就会被置为FALSE。在方法execPendingActions和execSingleAction被改变。

该字段被用来在 事务/出栈 被执行期间,防止用户手动commitNow。否则抛出异常:

1
throw new IllegalStateException("FragmentManager is already executing transactions");

mActive

从资源使用的效率方面考虑,FragmentManager维护了一个Fragment集合mActive,该集合中的所有Fragment都存在被FragmentManager使用的可能性,来执行add/show/remove/…等所有操作。
对于已经Add到view-container中的Fragment这个可能性是100%的,因为Fragment状态的切换都需要FragmentManager来管理,因此被add的Fragment必然存在于mActive的集合中;
而对于那些已经从view-container中移除的Fragment,取决于它们是否有再次被使用的可能性,例如退栈操作恢复上个事务状态时(backStackRecord.popFromBackStack()),可能有之前已经被移除的Fragment需要被重新添加到view-container,即表示这个Fragment就有被再次使用的可能性,所以对于回退栈中的所有事务中包含的Fragment,需要保留在mActive集合中。而退栈操作所抛弃的当前事务状态是无法回来的,可能有当前需要从view-container移除的Fragment,那么这些Fragment也就没有被再次使用的可能性,也就不需要再保留到mActive中。

存在于mActive集合中的Fragment会被 ++“标记”++ 为active状态,反之为inActive状态。
根据上面的分析,对于已经Add到视图容器的Fragment或者该Fragment被回退栈 “++持有++”,那么这个Fragment一定是active状态,因此方法isAdd()isInBackStack()可以用来判断一个Fragment是否处于active状态,除此之外,对于通过调用setRetainInstance(true)开启了设备配置改变时保留实例的特性的Fragment,在设备配置改变期间也不会被移除mActive集合。

注意

- `setRetainInstance(true)`特性对于那些在发生设备配置改变时之前已经被正常destroy的Fragment是无效的,即使是该Fragment开启了该特性。具体可参见[Fragment状态保存与恢复](http://whataa.github.io/2017/01/04/Fragment-Save%20and%20Restroe%20State/)一文。  
- `isInBackStack()`方法内部依据的是`mBackStackNesting`字段,具体可以参见`mBackStackNesting`的字段详解。
  • “持有”

    表示已经加入到回退栈的事务中包含有对该Fragment的操作,不论这个事务是否位于栈顶,例如:

    事务A是将Fragment1 add 到view-container,并且该事务调用了addToBackStack(String)加入回退栈,然后事务B是将Fragment2 replace 到view-container,并且该事务也加入了回退栈。

    此时由于当前回退栈中存在事务A,且事务A中拥有对Fragment1的操作,那么就表示回退栈就 持有 了Fragment1。

    按照正常流程,提交事务B后Fragment1会依次被destroyView、destroy、detach,默认情况下detach后Fragment会被标记为“inActive”,但是由于Fragment1被回退栈持有,这样Fragment只会被destroyView而无法继续后面的流程。也就是说:被关联的Fragment被执行remove时不会被标记为“inActive”状态。

  • “标记”

    标记的具体操作是将Fragment的mIndex字段赋值并将Fragment加入或移除mActive集合。
    如果是标记为active状态,那么mIndex > -1,且Fragment会被放入到了mActive集合中以mIndex为索引的对应位置;
    反之,mIndex被置为-1,并将mActive集合中对应处置为了null。

另外还可以参考以下文档:

mAdded

mAdded是mActive集合的一个子集,包含的仅是当前已经被添加到view-container中的Fragment。如果一个Fragment从view-container被移除,那么不管该Fragment是否会从mActive中被移除,都会从mAdded集合中移除。

我们可以通过Fragment的isAdded()方法来了解Fragment是否已经被Add,通常对区别mActive和mAdded集合很有效,可以参见Fragment的mAdded字段详解。

与mActive不同的是,位于mAdded集合中的Fragment由于当前正关联在Activity之上,所以可以接收一些由Activity分发/传递过来的事件,例如performLowMemory、performConfigurationChanged以及与Menu(ContextMenu、OptionsMenu等)相关的事件。

mAvailIndices

作用类似于mAvailBackStackIndices字段。为Fragment分配唯一索引ID。

mBackStack

表示FragmentManager所维护的回退栈。可以通过方法 getBackStackEntryAt(index) 和 getBackStackEntryCount() 来分别获取回退栈中指定index处的事务和回退栈的大小。

当提交的事务被执行(run())完成时,如果该事务开启了添加到回退栈的特性,FragmentManager会调用addBackStackState(BackStackRecord)方法将事务添加到mBackStack集合中并回调回退栈变化的监听(OnBackStackChangedListener)。

注意:回退栈的添加顺序与事务的执行顺序保持一致,因此FragmentManager限定了需要立即执行的事务无法添加到回退栈。

当触发出栈操作时,方法popBackStackState将栈中对应位置以上的事务全部执行了remove,具体参见BackStackRecord详解一文。

mCreatedMenus

首先需要查看Fragment的mHasMenu && mMenuVisible字段详解。

也是一个容纳Fragment的链表。该集合被FragmentManager用来记录开启了optionMenu的Fragment,以供稍后Fragment被hide/detach时用以回调onDestroyOptionsMenu()方法通知Fragment。

通过源码可知,optionMenu Fragment的流程分发最后是由FragmentManager的dispatchCreateOptionsMenu方法具体执行的,该方法先后依据以下三个条件来决定是否调用一个Fragment的onCreateOptionsMenu(...)方法,分别是:

  1. 是否存在于mAdded集合;
  2. 是否是SHOW状态(!hidden);
  3. 是否满足mHasMenu && mMenuVisible

如果三个条件都满足,Fragment的onCreateOptionsMenu(...)方法就会被回调然后通过Fragment#performCreateOptionsMenu(...)向FragmentManager返回true,最后FragmentManager会根据该返回状态将回调成功的Fragment添加到mCreatedMenus容器并向宿主Activity返回相同的状态。

但是若只要是任中一个条件不满足,Fragment的onCreateOptionsMenu(...)便不会被执行,并且此时还会对之前添加到mCreatedMenus容器的Fragment依次回调其onDestroyOptionsMenu()方法,最后FragmentManager的dispatchCreateOptionsMenu向宿主Activity返回False状态,宿主在收到该状态后便不会将稍后的onPrepareOptionsMenu(menu)流程分发给这个Fragment。

mBackStackIndices

一个ArrayList链表。索引处和索引处对应的值分别表示当前已被使用的事务ID和ID对应的事务。配合mAvailBackStackIndices链表一起为加入到回退栈的事务分配ID/回收ID。

这里的ID即Index,在回退栈中是唯一的,下同。为什么需要ID(Index),可以参见BackStackRecord的mIndex字段详解。

需要被添加到回退栈的事务被commit时,FragmentManager会调用方法allocBackStackIndex(BackStackRecord)为其分配ID,同时将生成的ID和事务本身添加到了mBackStackIndices集合中。
当回退栈中的事务被出栈执行时,集合mBackStackIndices对应ID处的事务就会被置为null。
另外,虽然在状态保存期间,该集合会丢失,但是在状态恢复期间恢复的回退栈时,会根据回退栈中事务的mIndex将该集合也一并恢复。

mAvailBackStackIndices

一个ArrayList链表,从字段名也可以看出,该集合包含的是可再次利用的事务ID,为需要加入到回退栈的事务提供ID。

集合中的ID的来源

该集合主要在出栈事务时收集被抛弃的事务的index。源码中共有两个方法向其添加元素:

  • 方法setBackStackIndex(int index, BackStackRecord bse)在状态恢复机制的mBackStack回退栈恢复期间被调用,作用是为了恢复mBackStackIndicesmAvailBackStackIndices集合。
  • 方法freeBackStackIndex在回退栈中的事务被出栈执行时popFromBackStack(...)方法时被调用:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void freeBackStackIndex(int index) {
    synchronized (this) {
    mBackStackIndices.set(index, null);
    if (mAvailBackStackIndices == null) {
    mAvailBackStackIndices = new ArrayList<Integer>();
    }
    mAvailBackStackIndices.add(index);
    }
    }

方法freeBackStackIndex将回退栈中对应index处的事务置为了null,并且将该index放入了mAvailBackStackIndices集合。也就是说,mAvailBackStackIndices这里收集的都是之前已经存在了的index,否则 在mBackStackIndices.set(index, null);这一行 就会抛出索引越界的异常, 而这些已经存在的index或者说这些已经存在在回退栈中的事务,只有当出栈的时候才会抛弃,也就是被BackStackRecord.popFromBackStack()调用。

可用ID是如何被使用的

参见BackStackRecord的mIndex字段详解,我们知道在commit时,FragmentManager的allocBackStackIndex方法负责为当前事务分配ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int allocBackStackIndex(BackStackRecord bse) {
synchronized (this) {
if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
...
int index = mBackStackIndices.size();
mBackStackIndices.add(bse);
return index;

} else {
int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);
mBackStackIndices.set(index, bse);
return index;
}
}
}

可以看到只要mAvailBackStackIndices不空,就会优先从其中取出可用的index,否则才会取栈顶的索引作为当前事务的ID。

该链表的意义

但是有一点不明白的是,结合上面的分析,其实每次从mAvailBackStackIndices取出的ID肯定就是当前回退栈的“栈顶”的索引(这里的栈顶指的是从栈的首部遍历,第一个为null的元素的索引),我们为什么不采用 在抛弃事务时就将其从栈中remove掉,入栈事务时直接add 这种方案呢,反而要维护一个多余的mAvailBackStackIndices集合?

对于回退栈,入栈出栈相较是比较频繁的,因此为了效率,这里才采用被抛弃的事务在回退栈中对应的位置的值只是被置为了null,而不是直接remove操作,并利用基本链表freeBackStackIndex来维护可用的事务ID,这样就可以有效的避免频繁申请/回收回退栈的空间,这是一种典型的以空间换时间的模式。

mBackStackChangeListeners

记录注册到FragmentManager的OnBackStackChangedListener的集合。OnBackStackChangedListener是监听FragmentManager维护的回退栈的变化的类,当回退栈添加事务或执行出栈操作,OnBackStackChangedListener类的onBackStackChanged()方法在这些操作执行完成后会被回调。

mCurState

FragmentManager维护下的Fragments的当前整体状态。由FragmentManager#moveToState(int, boolean)方法间接赋值改变:

1
2
3
4
5
6
7
8
void moveToState(int newState, boolean always) {
moveToState(newState, 0, 0, always);
}
void moveToState(int newState, int transit, int transitStyle, boolean always) {
...
mCurState = newState;
...
}

宿主Activity生命周期发生变化时通过 dispatchXXX 方法调用了moveToState(int, boolean),赋予当前Fragment与Activity生命周期对应的状态:
method | f.mState | newState | status
—|—|—|—
dispatchCreate() | Fragment.INITIALIZING | Fragment.CREATED | f.mState < newState
dispatchActivityCreated() | Fragment.CREATED | Fragment.ACTIVITY_CREATED | <
dispatchStart() | Fragment.ACTIVITY_CREATED | Fragment.STARTED | <
dispatchResume() | Fragment.STARTED | Fragment.RESUMED | <
dispatchPause() | Fragment.RESUMED | Fragment.STARTED | f.mState > newState
dispatchStop() | Fragment.STARTED | Fragment.STOPPED | >
dispatchReallyStop() | Fragment.STOPPED | Fragment.ACTIVITY_CREATED | >
dispatchDestroyView() | Fragment.ACTIVITY_CREATED | Fragment.CREATED | >
dispatchDestroy() | Fragment.CREATED | Fragment.INITIALIZING | >

moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive)会根据所操作的Fragment的状态f.mState和当前需要变化到的状态newState来进行状态转化:

注意:case之间并没有break,意味着每次moveToState时都是从Fragment的当前状态mState入口开始一直向下走直到最新的状态newState的所对应的case,期间会依次经历中间的状态;且case中执行相关操作(如performXXX)后都将f.mState更新为了当前case所对应的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
moveToState(Fragment, int, int, int, boolean) {
if (f.mState < newState) {
...
switch (f.mState) {
case Fragment.INITIALIZING:
...
case Fragment.CREATED:
...
}
} elser if (f.mState > newState) {
switch (f.mState) {
case Fragment.RESUMED:
...
case Fragment.STARTED:
...
}
}
// 各个版本不太一致,但是最后都是更新了Fragment的当前状态为最新(不一定,newState可能会在此之前被指定为特定状态)。
f.mState = newState;
}

具体调用流程和状态切换参见稍后的Fragment生命周期状态一文。

mHost

FragmentManager持有的宿主的引用,在宿主Activity的onCreate中将其传递给了FragmentManager。FragmentManager所有需要回调给宿主的操作/通知,都会通过它来传递。

mController

no desc

mContainer

表示FragmentManager所依附的容器,实则依旧是持有的宿主的引用,赋值方式与mHost一致。

当Fragment是以动态加载的方式关联到宿主时,在FragmentManager的moveTostate(5参)方法的升序Fragment.CREATED分支中,执行Fragment的onCreateView前会先根据Fragment的mContainerId为该Fragment查找其在宿主所依附的视图容器f.mContainer,查找的方式便是通过FragmentManager的mContainer来实现的:
mContainer调用的onFindViewById(@IdRes)方法内部实则是调用的宿主Activity的对应方法:

1
2
3
public View onFindViewById(int id) {
return FragmentActivity.this.findViewById(id);
}

上面涉及到的mContainerIdf.mContainer字段请参见对应的字段详解。

mParent

如果Fragment嵌套Fragment的情况,这个mParent指代的就是“父”Fragment。

sAnimationListenerField

该字段的引入实是为了修复setHWLayerAnimListenerIfAlpha(...)方法的一个动画监听器方面的BUG。

Fragment在执行相关动画时,会尝试开启硬件加速特性。当符合硬件加速条件,具体的流程是FragmentManager通过对anim设置动画监听,在动画开始前启用硬件加速并在动画结束时关闭硬件加速。
但是这里就存在一个BUG,因为Animation只能设置一个listener,那么在之前的v4版本中由于为开启硬件加速导致anim被重新设置了新的listener,若之前我们手动设置了listener,该listener就会被覆盖而失效。
因此sAnimationListenerField是用来反射Animation.classmListener字段得到我们手动设置的listener,最后在硬件加速重新设置listener时将之前的listener进行了包裹,使其可以正常回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void setHWLayerAnimListenerIfAlpha(final View v, Animation anim) {
...
if (shouldRunOnHWLayer(v, anim)) {
AnimationListener originalListener = null;
try {
if (sAnimationListenerField == null) {
sAnimationListenerField = Animation.class.getDeclaredField("mListener");
sAnimationListenerField.setAccessible(true);
}
// 拿到已经存在的listener
originalListener = (AnimationListener) sAnimationListenerField.get(anim);
} catch (...) {...}
ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, null);
anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener(v, anim, originalListener));
}
}

mNeedMenuInvalidate

首先需要查看Fragment的mHasMenu && mMenuVisible字段详解。

一个是否需要刷新optionsMenu或者说触发optionsMenu流程的标识。

每当事务被执行时,即FragmentManager执行add/show/…等操作时,在方法结束的最后都会根据所操作的Fragment的mHasMenu && mMenuVisible来开启mNeedMenuInvalidate标识。
然后当完成add/show/..,对所有Fragment执行moveToState进行整体状态切换后,会根据该标识来决定触发宿主的optionsMenu流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void moveToState(4参) {
...
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null) {
moveToState(f, newState, transit, transitStyle, false);
...
}
}
...
if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) {
// 这里:该方法是触发流程的入口
mHost.onSupportInvalidateOptionsMenu();
mNeedMenuInvalidate = false;
}
}

mStateSaved

mDestroyed

宿主是否执行destroy的标记,当宿主需要被销毁时,其中的FragmentMannager及其所有的Fragment都应该被释放掉。

  • 此时若是提交事务便会抛出异常:

    1
    2
    3
    4
    5
    6
    7
    public void enqueueAction(Runnable action, boolean allowStateLoss) {
    ...
    if (mDestroyed || mHost == null) {
    throw new IllegalStateException("Activity has been destroyed");
    }
    ...
    }
  • 此时若是有Fragment正在执行结束/退出动画(mAnimatingAway),会立即结束掉动画并执行动画后的操作,参见Fragment的mAnimatingAway字段详解。

mNoTransactionsBecause

ignore.

mHavePendingDeferredStart

具体参见Fragment的mDeferStart字段详解。

mStateBundle

一个临时Bundle容器。在状态保存机制触发回调Fragment的onSaveInstanceState(Bundle)时,作为该方法的参数用来临时存放Fragment用该方法保存的状态,该步骤执行完成后,该容器便转交给了FragmentManager的对应容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// FragmentManager#saveAllState() --> fs.mSavedFragmentState = saveFragmentBasicState(f)
Bundle saveFragmentBasicState(Fragment f) {
Bundle result = null;
if (mStateBundle == null) {
mStateBundle = new Bundle();
}
// 该方法内部调用`onSaveInstanceState(Bundle)`方法
f.performSaveInstanceState(mStateBundle);
if (!mStateBundle.isEmpty()) {
result = mStateBundle;
mStateBundle = null;
}
...
return result;
}

mStateArray

作用类似于上面的mStateBundle字段。同样是在状态保存机制触发,对Fragment的View层级状态进行保存时作为临时容器,然后将得到的状态交给了f.mSavedViewState,最后存放在存放Fragment状态所保存的容器中以FragmentManagerImpl.VIEW_STATE_TAG为key的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void saveFragmentViewState(Fragment f) {
if (f.mInnerView == null) {
return;
}
if (mStateArray == null) {
mStateArray = new SparseArray<Parcelable>();
} else {
mStateArray.clear();
}// 关键代码
f.mInnerView.saveHierarchyState(mStateArray);
if (mStateArray.size() > 0) {
f.mSavedViewState = mStateArray;
mStateArray = null;
}
}

Bundle saveFragmentBasicState(Fragment f) {
...
f.performSaveInstanceState(mStateBundle);
...
if (f.mView != null) {// 关键代码
saveFragmentViewState(f);
}
if (f.mSavedViewState != null) {
if (result == null) {
result = new Bundle();
}// 关键代码
result.putSparseParcelableArray(FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
}
...
return result;
}

mExecCommit

具体参见mPendingActions字段详解。

BackStackRecord

Op

表示一个对Fragment的操作,BackStackRecord是一个事务,构成事务的便是这1到多个的操作。

1
2
3
4
5
6
7
8
9
10
11
static final class Op {
Op next;
Op prev;
int cmd;
Fragment fragment;
int enterAnim;
int exitAnim;
int popEnterAnim;
int popExitAnim;
ArrayList<Fragment> removed;
}

从其数据结构来看,一个操作由8部分组成,其中最核心的就是操作的指令cmd以及该操作所要作用的对象Fragment。实际指令共有7种,分别是:

1
2
3
4
5
6
7
8
static final int OP_NULL = 0;// 无效指令
static final int OP_ADD = 1; // 对应BackStackRecord.add(...)
static final int OP_REPLACE = 2;// 对应BackStackRecord.repalce(...)
static final int OP_REMOVE = 3; // 对应BackStackRecord.remove(...)
static final int OP_HIDE = 4; // 对应BackStackRecord.hide(...)
static final int OP_SHOW = 5; // 对应BackStackRecord.show(...)
static final int OP_DETACH = 6; // 对应BackStackRecord.detach(...)
static final int OP_ATTACH = 7; // 对应BackStackRecord.attach(...)

其中需要说明的是OP_REPLACE指令,其原理是将已经add到指定container的所有Fragment先执行OP_REMOVE指令,最后再执行OP_ADD将指定的Fragment对象add到该container,因此Op对象的removed集合便是用来记录第一步中被remove的Fragment,该字段在其余cmd中无效。

每种指令对应的入口方法如上所示,它们内部调用的都是 adOp(Op) 方法来将每种操作封装成Op对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void addOp(Op op) {
if (mHead == null) {
mHead = mTail = op;
} else {
op.prev = mTail;
mTail.next = op;
mTail = op;
}
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
mNumOp++;
}

每个Op对象通过prevnext来记录其上一个和下一个操作分别是什么,由此多个操作便组成了一条双链表。另外,4个形如xxxAnim的字段表示的是该操作关联的动画,它们最终都是被事务对象的相应字段赋值,也就是说,一个事务下的每个操作不能单独指定动画,动画的最小粒度针对的是单个事务。动画可以在构建事务时通过方法setCustomAnimations(...)来指定,具体可以参见mEnterAnim等字段的详解。

mHead && mTail && mNumOp

分别表示事务的操作双链表的首部引用、尾部引用以及整个链表的长度,即该事务包含的指令数。

xxxAnim

mEnterAnim && mExitAnim && mPopEnterAnim mPopExitAnim

v4里使用的是Animation,app里使用的是Animator。

4种动画都是在构造事务时,调用setCustomAnimations(...)来指定的。在构造事务期间,方法addOp(Op)将其赋予到了每个指令Op对应的属性,而设置的动画最终在执行Op指令前(BackStackRecord的run方法或者popFromBackStack方法)都会赋值给fragment.mNextAnim,该属性由FragmentManager的loadAnimation方法调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// transit和transitionStyle是在构造事务时指定的,可以参见其字段详解
Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) {
// onCreateAnimation可以实现返回自定义动画,算是一个系统提供的HOOK方法
Animation animObj = fragment.onCreateAnimation(transit, enter, fragment.mNextAnim);
if (animObj != null) {
return animObj;
}
// 当然如果我们未实现上述方法,默认还会尝试读取指定的资源动画文件
if (fragment.mNextAnim != 0) {
Animation anim = AnimationUtils.loadAnimation(mHost.getContext(), fragment.mNextAnim);
if (anim != null) {
return anim;
}
}
// 其它:由transit指定的几个特定动画
...
}

  1. onCreateAnimation很重要,可以自定义一个Animation对象返回。
  2. loadAnimation优先使用fragment.mNextAnim指定的动画。

而loadAnimation被调用的时机共有2种:show/hideFragment、创建/销毁视图。
当执行showFragment或者在生命周期onCreateView与onViewCreated之间(仅限动态方式 创建的Fragment),enterAnim/popEnetrAnim会执行;当执行hideFragment或者在生命周期onDestroyView与onDestroy之间,exitAnim/popExitAnim会执行。
具体是执行mEnterAnim还是mPopEnterAnim或者mExitAnim还是mPopExitAnim,这与操作是否由出栈触发有关。 如果是执行事务时(run()),在上述的时机中,执行的是mEnterAnim和mExitAnim;而当出栈时(popFromBackStack(···)),上述时机执行的动画则变成了mPopEnterAnim和mPopExitAnim,并且两种情况都会覆盖为最新动画,因此在正常生命周期走到销毁或重建时,所触发的动画为最近一次改变的动画。

最后,在每次执行动画时,FragmentManager还是尝试对其启用 硬件加速 特性,具体可以参见FragmentManager的sAnimationListenerField字段详解。

mTransition && mTransitionStyle

mTransitionStyle仅在app.Fragment下有效,此处不分析。

在Fragment动画层面,mTransition 作为上面mEnterAnim等动画外的可选的补充,用以指定系统实现的3种类型共计6种实现的动画,可以在构造事务期间通过setTransition(int)来指定。

3种动画的取值分别是:
FragmentTransaction.TRANSIT_FRAGMENT_OPEN
FragmentTransaction.TRANSIT_FRAGMENT_CLOSE
FragmentTransaction.TRANSIT_FRAGMENT_FADE
前两种都是一个由ScaleAnimation和AlphaAnimation组合成的AnimationSet动画,最后一种是一个单独的AlphaAnimation动画。

作为补充动画,mTransition传递流程、触发时机与mEnterAnim等动画一模一样,而具体是选择mEnterAnim等动画还是mTransition,最终是由FragmentManager的loadAnimation方法来决定的,在mEnterAnim等动画字段的分析中,我们得出的结论是:首选由mEnterAnim等指定的动画,仅当它们没有指定,才会从transit中查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) {
...
if (transit == 0) {
return null;
}
// 根据enter取值,将3种类型具体分为了6种动画,选取其中一种返回
int styleIndex = transitToStyleIndex(transit, enter);
if (styleIndex < 0) {
return null;
}
// 根据动画索引生成具体的animation对象
switch (styleIndex) {
case ANIM_STYLE_OPEN_ENTER:
return makeOpenCloseAnimation(mHost.getContext(), 1.125f, 1.0f, 0, 1);
case ANIM_STYLE_OPEN_EXIT:
return makeOpenCloseAnimation(mHost.getContext(), 1.0f, .975f, 1, 0);
case ANIM_STYLE_CLOSE_ENTER:
return makeOpenCloseAnimation(mHost.getContext(), .975f, 1.0f, 0, 1);
case ANIM_STYLE_CLOSE_EXIT:
return makeOpenCloseAnimation(mHost.getContext(), 1.0f, 1.075f, 1, 0);
case ANIM_STYLE_FADE_ENTER:
return makeFadeAnimation(mHost.getContext(), 0, 1);
case ANIM_STYLE_FADE_EXIT:
return makeFadeAnimation(mHost.getContext(), 1, 0);
}
...
return null;

}

另外,再次强调一遍:所有动画仅针对以动态方式创建的Fragment。

mAddToBackStack

在mAllowAddToBackStack为TRUE的前提下(默认就是TRUE),当我们在构建事务时调用了addToBackStack(String)方法,那么mAddToBackStack就会被置为TRUE。

当在后续的提交/执行事务时,会根据该字段来决定是否为事务分配mIndex、是否需要加入回退栈、是否需要刷新mBackStackNesting(参见Fragment的mBackStackNesting字段详解)

mAllowAddToBackStack

表示当前事务是否被允许“保存”到回退栈,默认为TRUE即允许提交事务前调用BackStackRecord的addToBackStack(String)方法,如果调用了该方法,最后在执行完该事务时(run()),mManager.addBackStackState(this)会将其添加到回退栈集合。
如果我们提交事务采用的是commitNowXXX方法,即允许提交的事务立即执行,那么该事务就不被允许添加到回退栈中:

1
2
3
4
5
@Override
public void commitNowXXX() {
disallowAddToBackStack();
mManager.execSingleAction(this, boolean);
}

disallowAddToBackStack()mAllowAddToBackStack字段置为了FALSE,该方法与addToBackStack(String)互斥,禁止同时调用否则将抛出异常。

mName

一个可选字段,用以标识一个事务,不同的事务可以有相同的name,不会状态丢失。它是在构造事务期间调用addToBackStack(String name)入栈时传入的。

如果name存在,那么在稍后需要出栈时,可以根据该字段指定出栈的最终位置(参见BackStackRecord的出栈入栈分析一文)。

mCommitted

一个防止同一事务被多次提交的变量。

mIndex

仅对加入了回退栈的事务有效,用以标识该事务的唯一性,也可称ID,否则-1。

该字段的主要作用是为了出栈。具体体现为:由于回退栈中每个事务都有自己的唯一ID,因此在需要出栈时,可以根据该字段来指定出栈的终点(参见BackStackRecord的出栈入栈分析一文)。

对于一个要加入回退栈的事务,在commit时该事务会被分配一个唯一标识的ID:

1
2
3
4
5
6
7
8
9
10
int commitInternal(boolean allowStateLoss) {
...
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}

allocBackStackIndex在将事务加入回退栈的同时会返回事务对应的ID,这个ID是由当前回退栈存在的事务元素个数决定的(非null),具体可以参见FragmentManager的mAvailBackStackIndices字段详解。

xxxBreadCrumbxxx

mBreadCrumbTitleRes && mBreadCrumbTitleText && mBreadCrumbShortTitleRes && mBreadCrumbShortTitleText

形如文件管理器里面的路径排列的层级样式我们称作“Bread Crumb”。
例如:··· > aaa > bbbb > ccc > ···,点击aaa等按钮可以显示对应目录下的视图。

Android提供了一个叫做FragmentBreadCrumbs的类,该类是一个自定义ViewGroup,基本原理是利用Fragment的栈来实现上面的“Bread Crumb”,BreadCrumb的点击事件触发入/出栈、栈的更新事件又触发BreadCrumbs的更新。因此BreadCrumbTitleBreadCrumbShortTitle可以理解为存在于回退栈中的事务对应的title/subtitle,例如这里可以表示目录名。

但是从API21开始,可能是不符合MD设计模式的原因,该类被@Deprecated了。因此,这里的mBreadCrumbTitleRes等字段就基本没有存在的意义了。