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操作
replace通过doAddOp执行OP_REPLACE
都是通过addOp分别执行OP_REMOVE,OP_HIDE,OP_SHOW,OP_DETACH,OP_ATTACH
从上面的接口最终调用的函数来看,主要是doAddOp和addOp。doAddOp主要是addFragment相关的操作add和replace,需要处理containerId和tag。
5,commit和commitAllowingStateLoss
所以commit是个异步操作,FragmentManager的enqueueAction会将commit post到主线程Handler里。
Stack。至于每个Op的执行,除了replace,其他的都比较简单。
add/remove/show/hide/attach/detach的Op执行如下,比较简单,就是调用FragmentManager的对应的接口。
这里的两个关键点:
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里:
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。
至此FragmentTransaction的整个事务操作从perform一直到undo都完整的分析了,并且也对Fragment的Back Stack管理也介绍了,希望大家对Fragment的了解又加深了一些。下篇文章是Fragment系列的最后一篇了,介绍一下Fragment的进程状态保存和恢复逻辑,会解释commit的时候为什么会抛出state loss的exception,以及为什么要用setArguments来设置Activity的Bundle对象。
作者简介:
田力,网易彩票Android端创始人,小米视频创始人,现任roobo技术经理、视频云技术总监
欢迎关注微信公众号 磨剑石,定期推送技术心得以及源码分析等文章,谢谢
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技术经理、视频云技术总监
欢迎关注微信公众号 磨剑石,定期推送技术心得以及源码分析等文章,谢谢
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替