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本质之生命周期管理

Flutter Android 端 Activity/Fragment 流程源码分析

Flutter Android 端 Activity/Fragment 流程源码分析