Android Gems — Fragment本质之返回栈和事务管理
Posted threepigs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Gems — Fragment本质之返回栈和事务管理相关的知识,希望对你有一定的参考价值。
之前两篇文章 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 Gems — Fragment本质之返回栈和事务管理的主要内容,如果未能解决你的问题,请参考以下文章
Android Gems — Fragment本质之返回栈和事务管理
Android Gems — Fragment本质之返回栈和事务管理
Android Gems — Fragment本质之生命周期管理
Android Gems — Fragment本质之生命周期管理