您的位置:首页 > 移动开发 > Android开发

Android Gems — Fragment本质之返回栈和事务管理

2016-12-08 14:51 351 查看
之前两篇文章
Fragment本质之生命周期管理 和
Fragment本质之View管理,我们主要介绍了Fragment的整个生命周期管理,对Fragment的基本机制也了解得比较透彻了。这节我们介绍一下返回栈和事务管理。

所有用过Fragment的人对FragmentTransaction应该都不会陌生,这是fragment基本操作的接口。FragmentTransaction名字里带了transaction这个单词,也就意味着他是支持事务操作的,而事务是可以回退的。FragmentTransaction的本质就是一堆的Fragment Op(后续会分析FragmentTransaction的源码),FragmentManager除了管理Fragment本身之外,还会管理FragmentTransaction。FragmentTransaction以Stack的数据结构来存储,先进后出,Stack的名字就叫Back
Stack,当FragmentTransaction commit后执行的时候,就把自己压栈,而用户按Back键的时候,就会出栈,出栈的操作就等于是把之前压栈的FragmentTransaction的事务回退。当然,FragmentTransaction的事务功能是需要调用addToBackStack方法才会打开,默认情况下不知道BackStack回退。最后提一句,FragmentTransaction的实现类是BackStackRecord,也完美的体现了他就是Back Stack的一个节点。

我们接下来对BackStackRecord的源码进行分析:

1,add

add通过doAddOp执行OP_ADD操作

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

public FragmentTransaction add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}

public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
doAddOp(containerViewId, fragment, tag, OP_ADD);
return this;
}
2,replace

replace通过doAddOp执行OP_REPLACE

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

public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
if (containerViewId == 0) {
throw new IllegalArgumentException("Must use non-zero containerViewId");
}

doAddOp(containerViewId, fragment, tag, OP_REPLACE);
return this;
}
3,remove,hide,show,detach,attach

都是通过addOp分别执行OP_REMOVE,OP_HIDE,OP_SHOW,OP_DETACH,OP_ATTACH

public FragmentTransaction remove(Fragment fragment) {
Op op = new Op();
op.cmd = OP_REMOVE;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction hide(Fragment fragment) {
Op op = new Op();
op.cmd = OP_HIDE;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction show(Fragment fragment) {
Op op = new Op();
op.cmd = OP_SHOW;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction detach(Fragment fragment) {
Op op = new Op();
op.cmd = OP_DETACH;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction attach(Fragment fragment) {
Op op = new Op();
op.cmd = OP_ATTACH;
op.fragment = fragment;
addOp(op);
return this;
}
4,doAddOp和addOp

从上面的接口最终调用的函数来看,主要是doAddOp和addOp。doAddOp主要是addFragment相关的操作add和replace,需要处理containerId和tag。

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
fragment.mFragmentManager = mManager;
if (tag != null) {
if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
throw new IllegalStateException("Can't change tag of fragment "
+ fragment + ": was " + fragment.mTag
+ " now " + tag);
}
fragment.mTag = tag;
}
if (containerViewId != 0) {
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
}
看代码doAddOp主要是设置fragment的mContainerId,mFragmentId,mTag,之后就执行addOp完成操作。
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++;
}
Fragment的Op队列是个双链表,表头是mHead,表尾是mTail,addOp将op加到队列的末尾,然后设置进入、退出动画。看到这里就明白了,其他调用add/remove/show/hide/attach/detach/replace这些操作,这是讲Op加到了队列了,并未生效。而生效需要手动调用commit。

5,commit和commitAllowingStateLoss

public int commit() {
return commitInternal(false);
}

public int commitAllowingStateLoss() {
return commitInternal(true);
}

int commitInternal(boolean allowStateLoss) {
if (mCommitted) {
throw new IllegalStateException("commit already called");
}
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
PrintWriter pw = new FastPrintWriter(logw, false, 1024);
dump("  ", null, pw, null);
pw.flush();
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
两个commit最终都会执行到commitInternal方法,commitAllowingStateLoss和commit不一样的地方,我们留到下一篇Fragment的状态保存和恢复去分析。现在先看BackStackRecord的Op操作实现。这里可以看出commit也并没有马上执行其Op队列,而是enqueue给FragmentManager。如果调用了addToBackStack就返回其在back stack的序号,否则返回-1。

所以commit是个异步操作,FragmentManager的enqueueAction会将commit post到主线程Handler里。

public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}
这里可以看到enqueueAction是将action加到mPendingActions列表里,post mExecCommit到Handler里执行,而mExecCommit其实是执行execPendingActions,因此我们就看execPendingActions的实现:

public boolean execPendingActions() {
if (mExecutingActions) {
throw new IllegalStateException("Recursive entry to executePendingTransactions");
}
if (Looper.myLooper() != mHost.getHandler().getLooper()) {
throw new IllegalStateException("Must be called from main thread of process");
}
boolean didSomething = false;
while (true) {
int numActions;
synchronized (this) {
if (mPendingActions == null || mPendingActions.size() == 0) {
break;
}
numActions = mPendingActions.size();
if (mTmpActions == null || mTmpActions.length < numActions) {
mTmpActions = new Runnable[numActions];
}
mPendingActions.toArray(mTmpActions);
mPendingActions.clear();
mHost.getHandler().removeCallbacks(mExecCommit);
}
mExecutingActions = true;
for (int i=0; i<numActions; i++) {
mTmpActions[i].run();
mTmpActions[i] = null;
}
mExecutingActions = false;
didSomething = true;
}
......
return didSomething;
}
execPendingActions是在主线程执行的,遍历mPendingActions队列。每个Action执行run函数。因此就是执行BackStackRecord的run函数:

public void run() {
bumpBackStackNesting(1);
SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
calculateFragments(firstOutFragments, lastInFragments);
beginTransition(firstOutFragments, lastInFragments, false);
Op op = mHead;
while (op != null) {
switch (op.cmd) {
case OP_ADD:
break;
case OP_REPLACE:
break;
case OP_REMOVE:
break;
case OP_HIDE:
break;
case OP_SHOW:
break;
case OP_DETACH:
break;
case OP_ATTACH:
break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
}
op = op.next;
}
mManager.moveToState(mManager.mCurState, mTransition,
mTransitionStyle, true);
if (mAddToBackStack) {
mManager.addBackStackState(this);
}
}
上面就是BackStackRecord的run方法,具体每个Op的执行内容暂时省略。首先bumpBackStackNesting(1)对每个Op里的fragment的mBackStackNesting++,后面的firstOutFragments和lastInFragments是为了做fragment的transition动画用的,这里不展开分析。之后的while循环对每个Op,根据其op.cmd的类型做不同的操作。while完之后,就moveToState,将所有的Fragment的状态和FragmentManager的状态同步。最后,如果mAddToBackStack为true的话,就讲自己加入到FragmentManager的Back
Stack。至于每个Op的执行,除了replace,其他的都比较简单。

add/remove/show/hide/attach/detach的Op执行如下,比较简单,就是调用FragmentManager的对应的接口。

case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
}
break;
case OP_REMOVE: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.removeFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_HIDE: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.hideFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_SHOW: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.showFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_DETACH: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.detachFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_ATTACH: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.attachFragment(f, mTransition, mTransitionStyle);
}
再看replace操作,其实之前在生命周期里已经分析过,我们再回顾一下:首先查找mAdded队列里mContainerId等于给定containerId的那些fragment,加入到op的removed队列里,并对mBackStackNesting++后,逐个removeFragment。最后将要replace的Fragment addFragment,从而完成了replace操作。

case OP_REPLACE: {
Fragment f = op.fragment;
int containerId = f.mContainerId;
if (mManager.mAdded != null) {
for (int i = 0; i < mManager.mAdded.size(); i++) {
Fragment old = mManager.mAdded.get(i);
if (old.mContainerId == containerId) {
if (old == f) {
op.fragment = f = null;
} else {
if (op.removed == null) {
op.removed = new ArrayList<Fragment>();
}
op.removed.add(old);
old.mNextAnim = op.exitAnim;
if (mAddToBackStack) {
old.mBackStackNesting += 1;
}
mManager.removeFragment(old, mTransition, mTransitionStyle);
}
}
}
}
if (f != null) {
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
}
}
break;


这里的两个关键点:

a) 被replace的fragment需要加入removed列表,这个是因为后面事务回退的时候要把这些fragment再加回来。

b) mBackStackNesting需要+1,这样避免removeFragment的时候会将fragment从mActive移除。后续回退的时候还会把mBackStackNesting-1。

最后我们在总结一下FragmentTransaction的Op的执行过程:

1,调用add/replace/show/hide/attach/detach/remove接口
2,commit/commitAllowingStateLoss,enqueueAction到FragmentManager的PendingActions里,commit操作是异步的,不是调完就生效。
3,UI Handler会执行execPendingActions,此时FragmentTransaction的事务操作才是真正的执行,从而完成所有Op操作,并且将自己加入到Back Stack里。

6,FragmentManager的executePendingTransactions

前面说了commit/commitAllowingStateLoss的操作是异步的。如果需要马上生效的话也有方法,就是FragmentManager的executePendingTransactions方法,它会同步的执行execPendingActions方法,但要注意,一定要在主线程里调用,否则execPendingActions执行的时候会抛异常。

7,FragmentTransaction的事务回滚
事务的核心是其可以回滚的,回滚之后和执行这个事务之前能保持一样。FragmentManager的Stack叫做Back Stack,也就意味这,事务回滚的触发条件是back键,当back stack的层数大于1的时候,那么每次back键会将栈顶的BackStackRecord出栈,并将其事务回滚。

Back键的截获是在Activity的onBackPressed里:
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}

if (!mFragments.getFragmentManager().popBackStackImmediate()) {
finishAfterTransition();
}
}
会调用FragmentManager的popBackStackImmediate方法,当Back Stack元素大于1的时候,会return true,这样Activity就不会finish了,而是将栈顶的FragmentTransaction弹出栈,再把UI回退到前一次的Fragment状态。popBackStackImmediate代码就不分析了,他会找出要弹出的BackStackRecord,调用其popFromBackStack方法。

public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
bumpBackStackNesting(-1);
Op op = mTail;
while (op != null) {
switch (op.cmd) {
case OP_ADD:
break;
case OP_REPLACE:
break;
case OP_REMOVE:
break;
case OP_HIDE:
break;
case OP_SHOW:
break;
case OP_DETACH:
break;
case OP_ATTACH:
break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
}
op = op.prev;
}
......
if (mIndex >= 0) {
mManager.freeBackStackIndex(mIndex);
mIndex = -1;
}
return state;
}
popFromBackStack方法和run是对称的,一个是perform,一个是undo,是互逆的。bumpBackStackNesting(-1)先将fragment的mBackStackNesting-1,不只是对所有Op的fragment,并且还会对op的removed队列里的fragment也会mBackStackNesting-1,这是因为之前replace的时候对这些fragment做了mBackStackNesting+1,undo的时候也需要回滚。同run方法一样,链表里的每个Op都做undo。

add/remove/show/hide/attach/detach的Op执行如下,调用FragmentManager的对应的反接口。OP_ADD的回滚就是removeFragment,OP_REMOVE就是addFragment,OP_HIDE就是showFragment,OP_SHOW就是hideFragment,OP_DETACH就是attachFragment,OP_ATTACH就是detachFragment。

case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
mManager.removeFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
}
break;
case OP_REMOVE: {
Fragment f = op.fragment;
f.mNextAnim = op.popEnterAnim;
mManager.addFragment(f, false);
}
break;
case OP_HIDE: {
Fragment f = op.fragment;
f.mNextAnim = op.popEnterAnim;
mManager.showFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
}
break;
case OP_SHOW: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
mManager.hideFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
}
break;
case OP_DETACH: {
Fragment f = op.fragment;
f.mNextAnim = op.popEnterAnim;
mManager.attachFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
}
break;
case OP_ATTACH: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
mManager.detachFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
}
break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
}
最后是replace,先是将当前的fragment remove掉,然后把之前放到removed队列里的被替换的fragment再addFragment加回来。

case OP_REPLACE: {
Fragment f = op.fragment;
if (f != null) {
f.mNextAnim = op.popExitAnim;
mManager.removeFragment(f,
FragmentManagerImpl.reverseTransit(mTransition),
mTransitionStyle);
}
if (op.removed != null) {
for (int i = 0; i < op.removed.size(); i++) {
Fragment old = op.removed.get(i);
old.mNextAnim = op.popEnterAnim;
mManager.addFragment(old, false);
}
}
}
break;


至此FragmentTransaction的整个事务操作从perform一直到undo都完整的分析了,并且也对Fragment的Back Stack管理也介绍了,希望大家对Fragment的了解又加深了一些。下篇文章是Fragment系列的最后一篇了,介绍一下Fragment的进程状态保存和恢复逻辑,会解释commit的时候为什么会抛出state loss的exception,以及为什么要用setArguments来设置Activity的Bundle对象。

作者简介:

田力,网易彩票Android端创始人,小米视频创始人,现任roobo技术经理、视频云技术总监

欢迎关注微信公众号 磨剑石,定期推送技术心得以及源码分析等文章,谢谢

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android java fragment 源码