【Fragment】对Fragment的BackStackRecord事务的分析

继承于FragmentTransaction类的BackStackRecord是一系列对Fragment进行attach、add、replace、show、hide、remove、detach、anims操作的集合,我们称之为事务。事务减少了对Fragment的代码操作量,并且当事务被执行时,“批处理”极大地提升了处理效率。
事务同时提供了一个“保存”(addToBackStack(String))的概念,如果将提交的事务“保存”,那么“保存”的事务构成的集合就称为“回退栈(BackStack)”,“保存”也可以称为“入栈”。
回退栈的引出,使得Fragment拥有了类似于Activity栈的概念,意味着FragmentManager可以将呈现状态回到之前的某一个事务执行的结果,我们将这种特性称为“出栈”。

构建事务

BackStackRecord内部拥有一个静态类Op,代表着对一个操作的封装:包括了操作的指令cmd、操作的对象Fragment以及多个Op之间的先后顺序关系(next、prev)等。

1
2
3
4
5
6
7
8
// 可以参见Op的字段详解
static final class Op {
Op next;
Op prev;
int cmd;
Fragment fragment;
...
}

next、prev的存在将事务包含的操作集合定义为了一种双链表的结构,这种结构是有必要的,它既能保证事务提交后操作执行的严格的先后顺序,还可以非常方便的进行“反向”读取或操作,也就是我们所说的“出栈”。

对Fragment的操作一共有7种,分别是attach、add、replace、show、hide、remove、detach,BackStackRecord提供了相应的Op封装方法和指令来对应,例如add操作对应的三个重载方法:

1
2
3
public FragmentTransaction add(Fragment fragment, String tag);
public FragmentTransaction add(int containerViewId, Fragment fragment);
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag);

从参数来看,Fragment不一定需要依附到宿主的视图容器,并且我们还可以为add的Fragment指定一个tag来标识,可见这些方法不仅仅只是用来封装对应的操作,还展示了操作的使用规则。
三个方法的内部都调用了doAddOp方法并传入了OP_ADD指令,在doAddOp方法中,除了对参数的预检外,例如检查Fragment是否是一个静态的公共类、是否已经被add过等,最主要的操作便是构造Op对象来封装操作:

1
2
3
4
5
6
7
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
...
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
}

Op的构建就是简单的初始化一个Op对象,并关联对应的操作指令及操作对象Fragment。其实对于其余的操作,也都是利用的这样的构建方式来生成自己的Op对象,这也体现出事务是用来封装操作的集合而不是用来执行操作这样的特性。

得到每种操作对应的Op对象后,最后一步便是将这些Op“串”起来,所有操作的入口方法流程最终都会来到addOp(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对象添加到了链表的尾部,并且将每个Op对象的前后引用都指向了它的上一个和下一个操作构成了前面提到的双链表。
另外,事务提供了一个链表头mHead和链表尾mTail,这样当每次需要使用这些操作时,只要拿到双链表的表头或表尾就可以遍历到任何操作。
除了封装操作外,事务还提供了一些额外的功能例如:为当前事务指定进入/出栈等动画、为事务添加共享元素、将事务指定为加入回退栈等,可以参见对应的字段详解。

了解了事务提供了哪些功能以及这些功能内部是如何封装为Op对象后,要构造一个事务就很容易了。由此我们可以得出一个事务构造的大致模板:

1
2
3
4
5
6
7
8
9
Activity.this.getSupportFragmentManager().beginTransaction()
.attach(fragment)
.add(fragment, containerId, tag)
.show(fragment)
.replace(containerId, fragment, tag)
.hide(fragment)
.remove(fragment)
.detach(fragment)
.commit();

提交事务

根据是否允许事务立即执行和是否允许事务忽略状态丢失的风险,提交事务分为了4种方式:

1
2
3
4
public int commit();
public int commitAllowingStateLoss();
public void commitNow();
public void commitNowAllowingStateLoss();

其中方法名不带now的方法表示所提交的事务不需要立即执行;方法名带AllowingStateLoss的方法表示允许事务 提交时 (对于now就是执行时)忽略状态丢失的风险。

commit

不需要立即执行的提交方法内部都调用了commitInternal方法,对于事务,该方法又将其交给了FragmentManager的enqueueAction方法处理:

1
2
3
4
5
6
7
8
9
10
11
public void enqueueAction(Runnable action, boolean allowStateLoss) {
...
synchronized (this) {
...
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}

enqueueAction直接将事务添加到了等待集合mPendingActions中,并在等待集合只有1个事务时向消息池post了一个回调任务(mExecCommit)。可以看到,这种方案的内部是采用Handler机制来实现的。

Handler机制中存在一个消息池、一个不断循环读取分发消息池中的消息的Looper以及提交消息到消息池/处理Looper分发的消息的Handler,主线程中所有的操作都是由主线程的各个Handler提交并处理的,包括Activity生命周期的回调、View触摸事件的分发等。虽然消息池是一个队列结构且仅有一个主线程,使得每次只能处理一个消息,但是由于主线程的耗时机制,使得每个消息都必须在极短的时间内处理完成,因此我们平时所提交的消息都能“立即”执行,但其执行时机早已不是当时提交消息的那一刻了。

既然提交的事务不能立即执行,为什么又提供了这种提交方式?

这其实是为了效率。我们不可避免的会在同一时间提交多个事务,而事务的执行有相当部分是存在重复性的操作的,如果立即执行,对资源的消耗是可观的,为了避免这种情况,FragmentManager提供了mPendingActions字段来结合Handler实现该方案。
mPendingActions是一个Runnable集合,我们通过该方案提交的事务都被添加到了该集合,从上面的源码中可以看到,只要这个集合中一有事务,便立即利用宿主的Handler提交了一个回调任务(mExecCommit)到主线程的消息池中,

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

并在该操作之前调用removeCallbacks来保证回调任务mExecCommit未被处理前在消息池中的唯一性。
依据上面简述的Handler机制它并不会立即被处理,那么在mExecCommit还未被Looper从消息池中取出处理前,同一时间提交的多个事务就会被添加到mPendingActions中,之后当mExecCommit被回调,便触发execPendingActions()方法的执行,其内部实现是遍历mPendingActions,调用每个事务的run方法,这也就是所谓的触发执行事务:

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
public boolean execPendingActions() {
...
// 先决条件验证:是否正在执行、是否在主线程
...
while (true) {
int numActions;
// 同步操作,防止例如其它线程提交事务与当前主线程都同时操作mPendingActions的情况
synchronized (this) {
if (mPendingActions == null || mPendingActions.size() == 0) {
break;
}
numActions = mPendingActions.size();
if (mTmpActions == null || mTmpActions.length < numActions) {
mTmpActions = new Runnable[numActions];
}
// 批量转交给mTmpActions,
// 以让mPendingActions在事务执行期间有能力继续接收可能提交的事务
mPendingActions.toArray(mTmpActions);
mPendingActions.clear();
mHost.getHandler().removeCallbacks(mExecCommit);
}
mExecutingActions = true;
// 核心:依次调用run执行累积的事务
for (int i=0; i<numActions; i++) {
mTmpActions[i].run();
mTmpActions[i] = null;
}
mExecutingActions = false;
didSomething = true;
}
...
}

commitNow

立即执行的提交方案则没有commit方式的那样复杂的流程,因为是提交就立刻执行,所以不需要“等待集合”、不需要Handler,它更像一个精简版的“同步”commit,在提交后就立刻调用了FragmentManager的execSingleAction方法:

1
2
3
4
5
6
7
8
9
public void execSingleAction(Runnable action, boolean allowStateLoss) {
...
// 重复性提交拦截、主线程验证、状态丢失检查
...
mExecutingActions = true;
action.run();
mExecutingActions = false;
...
}

execSingleAction对于事务仅仅只是调用其run方法,由此可知,如果事务的个数、复杂度是可知可控的,相较于commit,commitNow方案是一个不错的选择。

commitNow是support包v24新增的API,在此之前如果我们想让当前提交的事务立即得到执行,只能曲折地在调用commit后立即调用executePendingTransactions方法,该方法会直接执行上面的execPendingActions方法。但是这样也导致之前commit的但是还未执行的事务即mPendingActions集合中的所有事务也会被执行。在某些情况,可能这并不是我们想得到的结果。

AllowingStateLoss

任何事务的执行都会引起FragmentManager内相关状态的改变,所以对每个提交的事务作状态丢失检查以确保在触发状态保存机制时能被正确地保存到状态是有必要的,除非我们认定所提交的事务引发的改变在状态保存机制被触发期间未被保存也无伤大雅。

以禁止状态丢失为例,检查状态具体来说就是在提交后准备期间的对应方法在中,调用了以下代码段来检查状态丢失:

1
if (!allowStateLoss) {checkStateLoss();}

方法名带AllowingStateLoss的方法将allowStateLoss字段置为了TRUE,而checkStateLoss方法则根据mStateSaved、mNoTransactionsBecause两个字段来决定是否抛出异常来终止操作:

1
2
3
4
5
6
7
8
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException("Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException("Can not perform this action inside of " + mNoTransactionsBecause);
}
}

mStateSaved

mStateSaved标识了宿主Activity下的整个Fragment体系是否已经发生了状态保存并且还未恢复,该字段在方法saveAllSate中被置为了TRUE(仅在版本大于3.0时,具体可以参见Fragment的状态保存和恢复一文),在分发其它生命周期例如create、resume等被置为FALSE(除stop外)。

由于该字段仅作为checkStateLoss()方法的判断依据,所以为了保证事务提交时机的正确性(即不允许在stop及之后提交事务),在Activity分发stop生命周期的时候也将mStateSaved置为了TRUE,而不管状态保存方法是否已被触发过了。

mNoTransactionsBecause

mNoTransactionsBecause是一个String字段,在LoaderManager回调onLoadFinished()onLoaderReset()方法时期会被赋值,由于Loader的异步机制,回调可能发生在Fragment的任何时期,包括在状态保存之后,所以这里干脆以一个显式的异常来提示我们来规避在上述两个回调中提交事务。

只要保证了提交事务时状态保存机制还未触发,那么在接下来事务是否已经执行已经不重要了,因为在触发状态保存机制导致FragmentManager的saveAllState方法执行时,会先确保还未执行的事务全部得到执行,因此在调用事务的run之前并不需要检查状态是否丢失的情况。

入栈

事务的另外一个很重要的特性便是“回退栈”概念。“入栈”开关是在事务构造期间调用addToBackStack方法开启的:

1
2
3
4
5
6
7
8
9
@Override
public FragmentTransaction addToBackStack(String name) {
if (!mAllowAddToBackStack) {
throw new IllegalStateException("This FragmentTransaction is not allowed to be added to the back stack.");
}
mAddToBackStack = true;
mName = name;
return this;
}

该方法开启了mAddToBackStack外,还可以为加入回退栈的当前事务指定一个name标识,方便后面出栈时指定出栈结果,两个字段具体都可以查看对应字段的详解。
mAllowAddToBackStack字段表示允许事务添加到回退栈,默认情况是TRUE,该字段存在的意义主要是为commitNow服务的,具体可以查看该字段的详解。这里需要弄清楚的是为什么通过commitNow提交的事务不被允许添加到回退栈?
这和两种提交执行的时机有关,由于commit方式提交的事务并没有立即执行,有可能在得到执行前又有几个事务被提交到了“等待集合”中,但是不论怎么样,它们都会按照提交顺序一次得到执行;但是对于commitNow方式,如果允许该方式提交的事务添加到回退栈,由于是立即执行,那么该事务在执行完成后就立刻被添加到了回退栈,但是此时仍有可能存在以commit方式提交但还未执行的的事务,这样就打乱了提交顺序与回退栈中元素顺序的一一对应关系,回退栈中元素顺序不一,那么在出栈时就存在回不到某个事务执行结果的情况,回退栈失去了存在意义。

继续分析,开启mAddToBackStack后,事务一旦被提交(以commit方式),在内部方法commitInternal中,首先会为该事务分配一个index,该字段是一个唯一标识,可以根据该字段精确地定位到回退栈中某个具体的事务,当然,和name字段一样,也可以作为出栈时方便指定出栈结果,与name不同的是,index是唯一的,而name可以重复或为null:

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);
...
}

分配index就是allocBackStackIndex方法来完成的,可以参见FragmentManager的mAvailBackStackIndices字段详解,解释了为何index便是该事务在回退栈中对应的元素位置。

接下来在事务开始执行的方法run()中,才是事务真正被添加到回退栈的实现:

1
2
3
4
5
6
7
@Override
public void run() {
...
if (mAddToBackStack) {
mManager.addBackStackState(this);
}
}

方法addBackStackState只做了两件事:将事务添加的回退栈链表中,回调回退栈变化事件:

1
2
3
4
5
6
7
void addBackStackState(BackStackRecord state) {
if (mBackStack == null) {
mBackStack = new ArrayList<BackStackRecord>();
}
mBackStack.add(state);
reportBackStackChanged();
}

到处,事务才算真正地入栈了,也就意味着FragmentManager可以将呈现状态回到之前的某一个事务执行的结果,具体在稍后的出栈章节中具体分析。

出栈

出栈一共有3种方式,分别是pop当前栈回到上一个事务的popBackStack()、pop到指定ID的对应事务为止的popBackStack(id, flags)以及pop到指定name的对应事务为止的popBackStack(name, flags),注意,这里的pop到指定name/id的对应事务为止表示回到该事务执行的对应结果,将栈中该事务以上的事务全部出栈(默认条件下,未开启Flag);

并且根据提交的及时性,FragmentManager一共提供了6个入口:

1
2
3
4
5
6
popBackStack()
popBackStackImmediate()
popBackStack(final String name, final int flags)
popBackStackImmediate(String name, int flags)
popBackStack(final int id, final int flags)
popBackStackImmediate(int id, int flags)

形如xxxImmediate(xxx)的方式表示会立即执行出栈操作,反之其余的是将整个操作提交给了Handler,在下一次事件轮询中才会被执行,与提交事务的几种方式类似。而所有方法内部的核心实现,都是转发给了FragmentManager的popBackStackState(handler, name, id, flags)方法。

FragmentManager#popBackStackState

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// FragmentManager
boolean popBackStackState(Handler handler, String name, int id, int flags) {
if (mBackStack == null) {
return false;
}
// 该分支表示第一种出栈方式:pop最上层栈,没有指定任何有效参数
if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0) {
int last = mBackStack.size()-1;
if (last < 0) {
return false;
}
final BackStackRecord bss = mBackStack.remove(last);
SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
if (mCurState >= Fragment.CREATED) {
// 根据Op双链表从尾至头开始为firstOutFragments和lastInFragments填充数据
// 原则:如果是add/relpace/show,则添加至firstOut;反正lastIn
// firstOutFragments和lastInFragments主要是为了执行API21以上的过渡动画和共享元素
bss.calculateBackFragments(firstOutFragments, lastInFragments);
}
// 开始真正的pop栈
// 由于只pop了一个元素,所以第一个参数为true,表示pop后就可以开始move-state
bss.popFromBackStack(true, null, firstOutFragments, lastInFragments);
// 最后,如果addOnBackStackChangedListener,这里会进行回调。
reportBackStackChanged();
} else {// else分支表示调用的方法是除了第一种外
int index = -1;
// 表示出栈方法指定了name或id任中一个
if (name != null || id >= 0) {
index = mBackStack.size()-1;
// 开始从栈顶向栈底遍历当前所有的栈,找出符合条件的栈,只要匹配一个就停止
while (index >= 0) {
BackStackRecord bss = mBackStack.get(index);
if (name != null && name.equals(bss.getName())) {
break;
}
if (id >= 0 && id == bss.mIndex) {
break;
}
index--;
}
// 当前集合中为空或没有指定条件的事务,直接返回
if (index < 0) {
return false;
}
// 如果指定了POP_BACK_STACK_INCLUSIVE这个FLAG,则继续遍历
// 如果在当前栈下还有N个连续的具有相同name或ID的事务,则记下最后一个匹配的事务
if ((flags&POP_BACK_STACK_INCLUSIVE) != 0) {
// 注意这里首先进行了减1操作,意味着即使下面的while没有找到直接break了,
// 也会将当前匹配的事务进行pop
index--;
// Consume all following entries that match.
while (index >= 0) {
BackStackRecord bss = mBackStack.get(index);
if ((name != null && name.equals(bss.getName()))
|| (id >= 0 && id == bss.mIndex)) {
index--;
continue;
}
break;
}
}
}
// 表示指定name/id的事务正好是当前所处的栈即最上层,并且没有指定FLAG,那么不需要执行任何操作
if (index == mBackStack.size()-1) {
return false;
}
final ArrayList<BackStackRecord> states = new ArrayList<BackStackRecord>();
// 从栈顶向下开始收集集合中位于匹配的栈之上的所有事务,供下面出栈操作使用
for (int i=mBackStack.size()-1; i>index; i--) {
states.add(mBackStack.remove(i));
}
final int LAST = states.size()-1;
SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
if (mCurState >= Fragment.CREATED) {
for (int i = 0; i <= LAST; i++) {
// 同if分支,遍历填充与过渡动画和共享元素相关的firstOutFragments、lastInFragments集合
states.get(i).calculateBackFragments(firstOutFragments, lastInFragments);
}
}
BackStackRecord.TransitionState state = null;
for (int i=0; i<=LAST; i++) {
// 遍历前面收集的需要pop的事务集合states,执行真正的pop
// i == LAST表示执行完所有事务的pop后,再通知FragmentManager进行统一的状态切换move-state
state = states.get(i).popFromBackStack(i == LAST, state,
firstOutFragments, lastInFragments);
}
// 同if分支,通知Listener
reportBackStackChanged();
}
return true;
}

依上流程,在返回栈中有元素的前提下,该方法一共包含了4个步骤,依次是:(1)找出此次出栈所要呈现的执行结果对应的终点事务;(2)在终点事务之前的事务集合中根据每个事务的Op链表从尾至头开始为firstOutFragments和lastInFragments填充数据以供API21后的设备执行过渡动画和共享元素操作;(3)执行popFromBackStack操作:执行动画并将终点事务之前的事务集合从栈顶向下依次真正出栈;(4)最后回调通知回退栈的改变事件。我们将一一进行分析。在分析第一个步骤前,我们先解释下POP_BACK_STACK_INCLUSIVE的含义。

POP_BACK_STACK_INCLUSIVE

在使用popBackStack(id, flags)popBackStack(name, flags)时,从源码中第一个while语句可以看出,该flag仅针对我们指定了name/id参数,且回退栈中存在指定name/id对应的事务的情况。

该Flag开启后,两个方法在 回退栈中存在name续相同的事务 的情况时(因为ID是唯一的),执行的结果是将栈内匹配到的最靠近栈底的事务及其之上的所有事务全部出栈,也就是说最后呈现的是靠近栈底的事务的下面一个事务执行的对应结果;
即使回退栈中不存在name连续相同(注意,这并不代表不存在name/id对应的事务)的事务的情况,popBackStack(id, flags)popBackStack(name, flags)最后呈现的也是指定name/id的对应事务的下面一个事务执行的对应结果。

查找终点事务

这个“终点”也就是我们此次出栈的目的:回到该事务对应的执行结果。终点的确定依据我们是否指定了参数name/id、参数是否存在以及是否开启了flag(指定flag为POP_BACK_STACK_INCLUSIVE):

  • 未指定任何参数

    表示我们使用的是popBackStack()方法。那么终点就是栈顶的下一个事务mBackStack.size()-2

  • 指定了 “空” 的name/id

    “空”指的是我们使用的是popBackStack(null, flag)popBackStack(-1, flag),如果此时未开启flag,则满足以下条件:

    1
    if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0)

    这种情况就和上面“未指定任何参数”的情况是一样的;但如果我们开启了flag,那么终点就是-1,意味着整个回退栈的事务都将被出栈,失去执行效果。

  • 指定了存在的name/id

    “存在”的意思就是回退栈中存在name/id对应的事务(1个或多个,id无多个)。在未开启flag的情况下,终点就是栈中从顶向下匹配到的第一个事务;一旦开启了flag,终点就会有另外两种可能:如果回退栈中存在name连续相同的事务,终点就是第一串连续相同name事务集中从顶向下匹配到的最后一个事务index的下一个事务,也就是index-1;如果不存在连续相同的,那么终点就是匹配到的第一个事务index的下一个事务——index-1。

    最后,如果index-1等于-1,那么情况就和上面“指定了空的name/id”一样。

  • 指定了不存在的name/id

    “不存在”的意思就是:我们指定了name/id,但是回退栈中并没有与之匹配的事务,注意与“空”的区别。这种情况下,在查找完成后由于未找到,源码中就直接return了,意味着方法执行不会改变任何结果。

填充firstOutFragments和lastInFragments

这一节与Transition动画相关,不影响整体的出栈流程,可以跳过。

查找到终点事务后,就需要将终点之前的事务全部执行除栈操作。但是由于API21开始引入了Transition的概念,所以对于Fragment,在出栈前还需要执行Transition动画。这一步就是为场景过渡(共享元素)动画作准备。

所谓的准备就是找出所有需要出栈的这些事务在即将出栈时总体上在每一个与Fragment相关的视图容器(conatainer)中,第一个被remove和最后被added的Fragment分别是哪个。BackStackRecord#calculateBackFragments会从每个事务的链表尾部开始遍历,根据以下原则来填充firstOutFragments和lastInFragments:

op.cmd 调用方法
OP_ADD setFirstOut
OP_REPLACE setFirstOut
OP_REMOVE setLastIn
OP_HIDE setLastIn
OP_SHOW setFirstOut
OP_DETACH setLastIn
OP_ATTACH setFirstOut

其中,OP_REPLACE操作下的op.removed触发执行的是setLastIn。
setFirstOut方法用于填充firstOutFragments,setLastIn方法用于填充lastInFragments。

我们将这种原则约定为“反转”机制。即之前该事务被commit到回退栈时执行的add/replace/show/attach操作,在即将出栈时,原先的操作都会转化为与之相对的操作。

这样对于每一个事务:之前最后add到Activity的Fragment现在就会最先被remove出去,所以调用setFirstOut来记录;最先remove出去的Fragment现在就会被最后重新add到Activity,所以调用setLastIn来记录。同时依据先前每个事务执行的先后顺序,越靠后执行的事务,现在应该越先被“反转”,所以在calculateBackFragments遍历时采取的顺序是从栈顶向栈底。

明白了setFirstOut和setLastIn被调用的依据原则,接下来我们来分析它们具体的填充规则。

setFirstOut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void setFirstOut(...) {
if (fragment != null) {
int containerId = fragment.mContainerId;
if (containerId != 0 && !fragment.isHidden()) {
if (fragment.isAdded() && fragment.getView() != null
&& firstOutFragments.get(containerId) == null) {
firstOutFragments.put(containerId, fragment);
}
// 容错处理,防止多个事务对同一个Fragment进行了不同操作
if (lastInFragments.get(containerId) == fragment) {
lastInFragments.remove(containerId);
}
}
}
}

其中的containerId保证了每个容器的firstOut的唯一性。Fragment能被作为firstOut的前提是:1.处于显示状态(!isHidden());2.已经被add(isAdded());3.mView不为空(getView() != null)。这几个条件很好理解,”First Out”的Fragment是需要执行“从有到无”的动画,如果Fragment并没有在当前页面或没有视图,动画也就失去了意义。

作为填充规则最后一环,firstOutFragments.get(containerId) == null用以筛选满足前置条件的所有Fragments,并将第一个put到了对应的容器,保证了“last in”的Fragment作为“first out”。

setLastIn

“last in”的Fragment是最终呈现到页面的Fragment的,因此它需要执行“从无到有”的动画。所以只要是没有被add的Fragment,会依次被put到lastInFragments以更新对应容器下记录的Fragment,那么最后一个被put的就表示“last in”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void setLastIn(...) {
if (fragment != null) {
int containerId = fragment.mContainerId;
if (containerId != 0) {
if (!fragment.isAdded()) {
lastInFragments.put(containerId, fragment);
}
// 同setFirstOut一样,容错处理
if (firstOutFragments.get(containerId) == fragment) {
firstOutFragments.remove(containerId);
}
}
if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED) {
mManager.makeActive(fragment);
mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
}
}

}

我们还可以发现,如果一个Fragment还处于初始状态INITIALIZING(其中一种情况就是该Fragment已经被dettach了),此时就会重新激活它makeActive并将其状态转到CREATED

popFromBackStack

执行过渡动画和共享元素

略。

真正的出栈

真正执行出栈操作的原理其实与上面“填充firstOut/lastIn”步骤的机制是一样的,就是执行事务的“反转操作”:从链表的尾部开始向前遍历,将每一个Op“原子”操作进行“反转”后再执行,规则如下:

op.cmd 调用方法 关联动画
OP_ADD mManager.removeFragment popExitAnim
*OP_REPLACE mManager.removeFragment popExitAnim
OP_REMOVE mManager.addFragment popEnterAnim
OP_HIDE mManager.showFragment popEnterAnim
OP_SHOW mManager.hideFragment popExitAnim
OP_DETACH mManager.attachFragment popEnterAnim
OP_ATTACH mManager.detachFragment popEnterAnim

其中OP_REPLACE操作的op.removed集合执行的是addFragment;另外OP_ATTACH操作的关联动画取的是popEnterAnim。

removeFragment等操作的具体流程可以参考Fragment生命周期状态分析,这里需要说明的是这些操作并不全都会对Fragment执行moveState,因此当此次出栈的所有事务已全部反转完成后(未完成的话FragmentManager会继续调用对应事务的popFromBackStack方法),还会通知FragmentManager开始执行一次整体的状态切换moveToState:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
...
// API>=21时,运行Transition
...
Op op = mTail;
while (op != null) {
// 反转操作:add-->remove、show-->hide ...
}
// 最后一个事务反转完成,doStateMove被标记为TRUE
if (doStateMove) {
// 开始整体的状态切换
mManager.moveToState(mManager.mCurState,
FragmentManagerImpl.reverseTransit(transition), transitionStyle, true);
state = null;
}
...
}

moveToState方法的详细流程可以参考Fragment生命周期状态分析一文,此处不再累述。

回调通知回退栈的改变事件

这一步就很简单了,依次执行完真正的出栈操作后,FragmentManager中的popBackStackState方法在return前调用了reportBackStackChanged方法,该方法就是依次调用注册到mBackStackChangeListeners的监听的回调方法,通知事件的改变:

1
2
3
4
5
6
7
void reportBackStackChanged() {
if (mBackStackChangeListeners != null) {
for (int i=0; i<mBackStackChangeListeners.size(); i++) {
mBackStackChangeListeners.get(i).onBackStackChanged();
}
}
}

listener则可以通过getSupportFragmentManager().addOnBackStackChangedListener来注册。

执行事务

其实BackStackRecord不仅继承了FragmentTransaction,还实现了Runnable接口,这也是为什么可以被直接加入“等待集合”的原因。
不管是哪一种提交方式,事务的执行最终都回到了BackStackRecordrun()方法,该方法总体分为了四个部分,这与出栈的操作流程大致是相同的,分别是:(1)对API>=21版本的设备计算并执行共享元素相关的过渡动画transitions;(2)遍历事务操作指令并依次委托FragmentManager执行真正的操作;(3)当所有的指令执行完成后,统一进行状态切换moveToState调动Fragment生命周期;(4)入栈事务并通知回退栈改变世事件。接下来,我们依次对这几部分进行分析。

(1)计算并执行动画(API>=21)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void run() {
// 改方法主要是为Fragment计数:某个Fragment被某个事务“引用”的次数,
// 若为0则表示该Fragment“不在”回退栈中。
bumpBackStackNesting(1);
TransitionState state = null;
SparseArray<Fragment> firstOutFragments = null;
SparseArray<Fragment> lastInFragments = null;
if (SUPPORTS_TRANSITIONS && mManager.mCurState >= Fragment.CREATED) {
firstOutFragments = new SparseArray<Fragment>();
lastInFragments = new SparseArray<Fragment>();

calculateFragments(firstOutFragments, lastInFragments);

state = beginTransition(firstOutFragments, lastInFragments, false);
}
...
}

其中的核心方法就是计算并填充firstOutFragments, lastInFragments集合的calculateFragments方法,以及执行动画的beginTransition方法。
此处的流程与出栈对应的流程是一样的,唯一的区别便是出栈时是将指令进行“反转”分析,导致最先被add的需要变为最后被remove,最后被remove的需要变为最先被add,并添加到对应的链表;而这里只需按照指令的先后顺序分析即可无需“反转”,因此调用了方法calculateFragments而不是calculateFragments方法。

注意

这里同样需要注意的是在遍历Ops查找last in的时候,对于第一次将被add或者先前被dettach/destory、现在重新需要add 的Fragment(即是Fragment的mState为INITIALIZING),这里会将其状态state 提前 moveto至CREATED,也就意味着Fragment的生命周期方法至少会提前走到onCreate。这与API<21的版本中的Fragment的事务执行顺序显著不同。

API<21的版本中,在开始run事务集合时,add、replace操作对Fragment的生命周期状态的效果并不会立即切换,而是在整个Ops集合遍历完成时才开始,可以参见BackStackRecord的run方法;也因此如果Ops集合中有hide / show等操作时,即使add是先于其执行,但是onHiddenChanged回调仍会先于onAttach;而API>=21中,calculateFragments方法打乱了这个顺序。

(2)执行真正的指令操作

遍历指令之前,先拿到指令备选动画transition、transitionStyle,至于为什么是备选,可以参见对应的字段详解,这些备选动画在最后一步执行状态切换moveToState时如果指令对应的anim没有指定时就会起效:

1
2
int transitionStyle = state != null ? 0 : mTransitionStyle;
int transition = state != null ? 0 : mTransition;

接下来拿到事务指令双链表的头部mHead开始遍历并执行具体操作,具体的对应原则如下表所示:

op.cmd 调用方法 f.mNextAnim
OP_ADD addFragment enterAnim
OP_REPLACE addFragment enterAnim
OP_REMOVE removeFragment exitAnim
OP_HIDE hideFragment exitAnim
OP_SHOW showFragment enterAnim
OP_DETACH detachFragment exitAnim
OP_ATTACH attachFragment enterAnim

其中, OP_REPLACE指令在addFragment前会先remove掉依附的视图容器中的其它已经add的Fragment,对应的动画为exitAnim。

addFragment等方法的具体执行流程可参见Fragment生命周期状态一文。
当所有的指令执行完成后,不管在此期间某些操作是否触发了对应Fragment的状态切换moveToState,最后mManager仍然会调用moveToState方法做一次其维护下的所有Fragments整体的状态切换,以确保所有的Fragment的生命周期保持与宿主一致。

(3)moveToState状态切换

具体参见Fragment生命周期状态一文。

(4)通知回退栈改变世事件

执行完状态切换后,对于开启了mAddToBackStack的事务,除了将其添加到回退栈的事务,在此还需要对注册了OnBackStackChangedListener的listener回调回退栈的改变:

1
2
3
4
5
6
7
8
// 流程:run()->addBackStackState(state)->reportBackStackChanged()
void reportBackStackChanged() {
if (mBackStackChangeListeners != null) {
for (int i=0; i<mBackStackChangeListeners.size(); i++) {
mBackStackChangeListeners.get(i).onBackStackChanged();
}
}
}

结合出栈章节的分析,不管是出栈还是入栈,事件都得到了正常回调。