欢迎光临散文网 会员登陆 & 注册

Fragment事务管理源码分析

2021-09-17 21:24 作者:房顶上的铝皮水塔  | 我要投稿

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

FragmentActivity#getSupportFragmentManager

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


FragmentTransaction#add

我们通常使用这样的形式向指定的ViewGroup中添加Fragment:

包括很多的不同的add方法的重载,这些方法都会最终调用Fragment#doAddOp:

Fragment#doAddOp:

这一个方法主要是给Fragment设置相关的参数,主要做了三件事情:

公平 公平 还是tmd公平!
  1. tag

  2.  containerViewid

  3. 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,但是这样会带来不好的用户体验。考虑下面两种情形:

  1. Activity由于发生了旋转,Activity调用onSaveInstanceState恢复之前的状态,但是此时commit在onSavedInstanceState之后调用,本来想更新新的内容,但是却恢复成了原来的内容,这个时候由于onSavedState=true,就回抛出异常。

  2. 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的流程如图所示:

zoingjie 

总结:

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进入了回退栈,返回的时候会回滚事务。

Fragment事务管理源码分析的评论 (共 条)

分享到微博请遵守国家法律