Fragment事务管理源码分析

一个简单的FragmentTranscation事务相关的源码分析和面试题的回答~
FragmentActivity#getSupportFragmentManager


通过FragmentManager获取FragmentTransaction对象,实际上返回的是BackStackRecord这个类。FragmentTransaction是一个抽象类

FragmentTransaction#add
我们通常使用这样的形式向指定的ViewGroup中添加Fragment:
包括很多的不同的add方法的重载,这些方法都会最终调用Fragment#doAddOp:
Fragment#doAddOp:
这一个方法主要是给Fragment设置相关的参数,主要做了三件事情:

tag
containerViewid
fragmentId
最后将Fragment和opCmd(在这里就是OP_ADD)封装成一个Op对象。

FragmentTransaction#addOp
然后就存放到了一个ArrayList中:
FragmentTransaction#replace remove hide detach attach
这些方法以及它的重载方法都是调用了doAddOp,但是cmdOp会使用不同的枚举:
具体的看看源代码就行~
FragmentTransaction#commit
FragmentTransaction 自身并没有实现commit,真正的实现方法在BackStackRecord中。我们常用的commit和commitAllowingStateLoss都会调用commitInternal方法:
只不过commit中allowingStateLoss=false,commitAllowingStateLoss中为true。
在方法的尾部会调用FragmentManager#enqueueAction。这里传递的参数是this,所以BackStackRecord从FragmentTransaction中继承的Op集合也会被传过去。
FragmentManager#enqueueAction
Fragment状态的保存是在Activity#onSaveInstanceState时进行的,调用FragmentManager#saveAllState保存,并且设置mStateSaved为true。而istStateSaved会检查mStateSaved || mStopped;是否等于true,如果等于true就回抛出异常。
所以我们应该尽量避免在onStart、onResume中commit,因为这写方法可能会被反复调用。
这里参考了这篇文章:cnblogs.com/kissazi2/p/4181093.html
所以,如果是commitAllowingStateLoss,FragmentManager在commit的时候就不会去检查是否会发生stateLoss,但是这样会带来不好的用户体验。考虑下面两种情形:
Activity由于发生了旋转,Activity调用onSaveInstanceState恢复之前的状态,但是此时commit在onSavedInstanceState之后调用,本来想更新新的内容,但是却恢复成了原来的内容,这个时候由于onSavedState=true,就回抛出异常。
Activity被回收了,mStopped=true,这个时候commit修改Activity的内容也是无效的。
所以我们不应该假设Activity不会发生stateLoss而调用FragmentTransaction#commitAllowingStateLoss。

回到我们原本的方法,enqueueAction也加了同步锁机制,然后将当前的Action(BackStackRecord)添加到mPendingActions中,然后调用scheduleCommit:
FragmentManager#scheduleCommit
这个方法中会对加入到pendingActions中的操作进行同步锁定,然后通过Activity的Handler发送出去:
在mExecCommit这个Runnable中实际上就回最终调用到我们BackStackManager#executeOps方法,在这个方法中会根据之前每一个Op实例设置的cmd执行对应的FragmentManager的操作。所以commit的流程如图所示:

总结:
1. commit和commitAllowingStateLoss
这两个方法都会调用commitInternal,前者不允许stateLoss,后者允许。在Activity#onSavedInstanceState中会设置FragmentManager的mSavedState=true,如果我们在这个方法之后commit会抛出异常;同时我们也不应该在Activity Stop之后commit。
2. commit 是异步的
我们在commit的时候最终会将操作添加到FragmentManager中的队列中,最后通过Handler发送给主线程执行。所以多一个transaction.commit的时候不能保证这个动作立刻执行。而且transaction的动作,比如remove add replace都是在commit执行时才会发生。如果想要立刻执行事务
1. 可以使用commitNow()【commitNow不会将事务添加到回退栈中】。而且不可以和回退栈一起使用,以下的代码会报错。
因为:
而在commitNow中会调用:
2. 可以使用FragmentManager.executePending
但是Android SDK的注释中也指出,使用这种方式会导致一些副作用,因为这种操作会执行所有待执行的任务。
3. 重建Fragment
当Activity意外销毁时,会保存当前Fragment的状态和Arguments。在这篇文章
https://juejin.cn/post/6984605769245130788#heading-24 中指出一点需要注意的问题,FragmentManager#restoreAllState方法会调用反射调用Fragment的无参构造方法恢复Fragment。所以尽量使用默认无参构造方法,而且获取参数的同时,使用arguments。
不过这点我在androidx的代码中看起来和文章中并不一样,可能只是我没看到.....
但是还是可以注意一下~
4. 如何获取FragmentManager?
在Activity中使用getSupportFragmentManager,在Fragment中使用getChildFragmentManager。这个变量在Fragment初始化的同时也会被初始化。
5. 不要使用startActivityForResult,应该使用Activity Results API
https://segmentfault.com/a/1190000037601888
6. Fragment的回退栈
所以实际上并不是Fragment加入了回退栈,而是Fragment的事务进入了回退栈。如果当Fragment进入了回退栈,返回的时候会回滚事务。