转自:伤心的猪大肠
https://juejin.cn/post/6892608279140204551
本文基于androidx版本的Fragment
如果要是有面试官问你,Fragment是如何进行事务管理的?相信很多人可能都回答不出来,很多人可能觉得问题这个还不如问如何进行Fragment预/懒加载,生命周期,返回栈,状态保存等来的有水平。其实我觉得这个问题其实问的挺有深度,如果你想要了解Fragment的事务管理,需要对Fragment的源码进行充分了解,前面的那些问题都是小儿科了。
面试的时候问题有两种类型,一种是问典型,比如面试官问你HashMap,里面涉及到的方方面面非常多,可以进行大量扩展;还有一个就是直接问一定深度的问题,如果你能答上来,说明的掌握的到一定的程度,简单的问题就可以忽略了,答不上了我再问前面简单的问题。
要了解Fragment,首先需要了解它的生命周期,同时与Activity的生命周期的关联也是非常重要的。


Fragment是依附于Activity,所以Fragment的生命周期和Activity的生命周期息息相关,在每个Activity的生命周期中最终都会调用FragmentManagerImpl.dispatchXXX()通知,然后调用到FragmentManagerImpl.dispatchStateChange(int nextState),Fragment有多个状态值来展示什么周期所处的状态
static final int INITIALIZING = 0; // Not yet created.static final int CREATED = 1; // Created.static final int ACTIVITY_CREATED = 2; // Fully created, not started.static final int STARTED = 3; // Created and started, not resumed.static final int RESUMED = 4; // Created started and resumed.//Fragment 默认的状态是 INITIALIZING,也是代表当前的生命周期状态int mState = INITIALIZING;
注意本文用的是androidx版本的fragment,而非普通support包下面的fragment(有六种状态),最终都会调用到moveToState方法,然后进行这些状态的变化,同时调用fragment具体的生命周期方法。
先来看看Fragment中的事务(FragmentTransaction)的action
static final int OP_NULL = 0;static final int OP_ADD = 1;static final int OP_REPLACE = 2;static final int OP_REMOVE = 3;static final int OP_HIDE = 4;static final int OP_SHOW = 5;static final int OP_DETACH = 6;static final int OP_ATTACH = 7;static final int OP_SET_PRIMARY_NAV = 8;static final int OP_UNSET_PRIMARY_NAV = 9;static final int OP_SET_MAX_LIFECYCLE = 10;
所有的事务操作被封装为一个Op类进行统一管理
static final class Op {int mCmd;Fragment mFragment;int mEnterAnim;int mExitAnim;int mPopEnterAnim;int mPopExitAnim;Lifecycle.State mOldMaxState;Lifecycle.State mCurrentMaxState;Op() {}Op(int cmd, Fragment fragment) {this.mCmd = cmd;this.mFragment = fragment;this.mOldMaxState = Lifecycle.State.RESUMED;this.mCurrentMaxState = Lifecycle.State.RESUMED;}Op(int cmd, Fragment fragment, Lifecycle.State state) {this.mCmd = cmd;this.mFragment = fragment;this.mOldMaxState = fragment.mMaxState;this.mCurrentMaxState = state;}}
先来看一下事务添加的流程

首先是通过FragmentTransaction的add/replace/show/hide/remove/attach等操作,将这些操作(Op)添加到ArrayList<Op> mOps = new ArrayList<>()中,后面进行commit的时候,FragmentTransaction是个抽象类,真正的实现类是BackStackRecord,然后进行commit,判断用户是否添加到回退栈中,然后将操作交给FragmentManagerImpl,进行事务的入队,开始处理事务集合,最终进行到moveToState方法,执行Fragment的生命周期的方法。

ArrayList<BackStackRecord> mBackStackIndices;ArrayList<Integer> mAvailBackStackIndices; //可用的回退栈的索引
这里要说一下这两个数组,只有加入到回退栈,才会进行管理。回退栈的添加需要手动调用具体方法。
一个是管理回退栈中的所有事务,一个是用于记录索引的。如上图所示,比如mBackStackIndices数组中有5个BackStackRecord,当你移除掉1,3两个(同时把这两个位置置为null),然后就会把索引添加到mAvailBackStackIndices数组(第一个和第二个位置上),相当于在把两个位置记录下来,下次再添加到返回栈中,通过判断mAvailBackStackIndices中的内容就可以判断出mBackStackIndices中有哪些位置是空的,直接在空的位置中放入新添加BackStackRecord,提高数组的复用效率,之前的版本使用链表来管理的,但是效率上来讲还是不如这种方式来得高。
事务,一组操作全部要做,要么全部不干,流程绑在一起用,保证连续的操作是原子性的。
commit()commitAllowingStateLoss()commitNow()commitNowAllowingStateLoss()
commit并不是立即执行的,它会被发送到主线程的任务队列当中,当主线程准备好执行它的时候执行。
//BackStackRecord.java//commitpublic int commit() {return commitInternal(false);}//commitInternalint commitInternal(boolean allowStateLoss) {···mCommitted = true;if (mAddToBackStack) {mIndex = mManager.allocBackStackIndex(this);} else {mIndex = -1;}mManager.enqueueAction(this, allowStateLoss);return mIndex;}
然后是FragmentImpl中的enqueueAction方法
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {if (!allowStateLoss) {checkStateLoss();}synchronized (this) {if (mDestroyed || mHost == null) {if (allowStateLoss) {// This FragmentManager isn't attached, so drop the entire transaction.return;}throw new IllegalStateException("Activity has been destroyed");}if (mPendingActions == null) {mPendingActions = new ArrayList<>();}mPendingActions.add(action);scheduleCommit();}}
scheduleCommit方法
void scheduleCommit() {synchronized (this) {boolean postponeReady =mPostponedTransactions != null && !mPostponedTransactions.isEmpty();boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;//通过handler发送if (postponeReady || pendingReady) {mHost.getHandler().removeCallbacks(mExecCommit);mHost.getHandler().post(mExecCommit);updateOnBackPressedCallbackEnabled();}}}
popBackStack()也是这样,发送到主线程的任务队列中,也就是说他们都是异步的。
commitNow是同步的,保证立即执行,但是不会加入到回退栈当中,因为可能会扰乱回退栈中内容的顺序,还有另外两个方法的详细内容可以看这篇文章。
public void commitNow() {disallowAddToBackStack();mManager.execSingleAction(this, false);}public void execSingleAction(OpGenerator action, boolean allowStateLoss) {···ensureExecReady(allowStateLoss);if (action.generateOps(mTmpRecords, mTmpIsPop)) {mExecutingActions = true;try {removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);} finally {cleanupExec();}}updateOnBackPressedCallbackEnabled();doPendingDeferredStart();burpActive();}
然后是ensureExecReady方法
private void ensureExecReady(boolean allowStateLoss) {···mExecutingActions = true;try {executePostponedTransaction(null, null);} finally {mExecutingActions = false;}}
最终执行到
public void completeTransaction() {final boolean canceled;canceled = mNumPostponed > 0;FragmentManagerImpl manager = mRecord.mManager;final int numAdded = manager.mAdded.size();for (int i = 0; i < numAdded; i++) {final Fragment fragment = manager.mAdded.get(i);fragment.setOnStartEnterTransitionListener(null);if (canceled && fragment.isPostponed()) {fragment.startPostponedEnterTransition();}}mRecord.mManager.completeExecute(mRecord, mIsBack, !canceled, true);}
当Activity在后台被回收或者App的进程处于Sleep状态等特殊情况时候会调用ActivityThread.callActivityOnSaveInstanceState => Instrumentation.callActivityOnSaveInstanceState => Activity.performSaveInstanceState => Activity.onSaveInstanceState => FragmentActivity.onSaveInstanceState 来保存数据,最终将数据保存到ActivityClientRecord的成员变量state中。
当Activity恢复的时候创建一个新的Activity,执行FragmentActivity.onCreate,然后执行FragmentController.restore=>FragmentManagerImpl.restoreSaveState恢复数据,然后FragmentController.dispatchonCreate =>FragmentManagerImpl.dispatchCreate`重走Fragment的生命周期。
主流的APP中首页底部一般有四个按钮,点击底部的不同界面,分别显示不同的界面(Fragment),这个时候通常使用ViewPager+多个Fragment。如果你想要预先加载多个Fragment,通常使用以下方法进行Fragment的预加载
viewPager.setOffscreenPageLimit(int count);
预加载viewpager.setOffscreenPageLimit(),这个设置的值有两层含义: 一是 ViewPager 会预加载几页; 二是 ViewPager 会缓存 2n+1 页(n为设置的值),但是如果你不想与预加载数据呢,设置值为0如何?通过查看源码,如果传入的值小于1,那么ViewPager就会把预加载数量设置成默认值,而默认值就是1,所以说就算你传入了0,ViewPager还是会预先加载一个界面。
所谓的懒加载,就是结合生命周期判断fragment是否为可见状态,根据可见状态判断是否加载数据。
使用setUserVisibleHint或者onHiddenChanged进行辅助判断,show和hide的时候不会调用生命周期的方法,而是会调用onHiddenChanged,详细的设置可以参考该文章
androidx中setUserVisibleHint已经被废弃,推荐我们使用FragmentTransaction的setMaxLifecycle方法,而且androidx对Fragment中的生命周期进行了大规模的重构,已经和之前的版本有很大不同了,先来看下面这张图

图中展示了Fragment状态间切换会执行生命周期以及Lifecycle.State对应的Fragment状态,setMaxLifecycle方法要求传入的状态至少为CREATED
/*** Set a ceiling for the state of an active fragment in this FragmentManager. If fragment is* already above the received state, it will be forced down to the correct state.** <p>The fragment provided must currently be added to the FragmentManager to have it's* Lifecycle state capped, or previously added as part of this transaction. The* {@link Lifecycle.State} passed in must at least be {@link Lifecycle.State#CREATED}, otherwise* an {@link IllegalArgumentException} will be thrown.</p>** @param fragment the fragment to have it's state capped.* @param state the ceiling state for the fragment.* @return the same FragmentTransaction instance*/public FragmentTransaction setMaxLifecycle( Fragment fragment,Lifecycle.State state) {addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));return this;}
下面来看看懒加载的方案,我们可以发现FragmentPagerAdapter的构造方法过时了
public TabFragmentPagerAdapter(FragmentManager fm, List<Fragment> list) {super(fm); //该方法过时了this.mlist = list;}
可以看到内部又调用了两个参数放到方法,我们来看一下这个新的构造方法
public FragmentPagerAdapter(@NonNull FragmentManager fm,@Behavior int behavior) {mFragmentManager = fm;mBehavior = behavior;}
多了一个int类型的参数
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
一个参数的构造方法默认传入BEHAVIOR_SET_USER_VISIBLE_HINT,如果你使用两个参数的构造方法,传入的是BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,会调用setMaxLifecycle()方法将上一个Fragment的状态设置为STARTED,将当前要显示的Fragment的状态设置为RESUMED;如果设置的值为BEHAVIOR_SET_USER_VISIBLE_HINT,最终调用到的还是setUserVisibleHint()方法,懒加载方法参考第一种。
调用两个参数的构造方法后,在Fragment变为可见时都会调用onResume方法,我们可以用这一点来实现懒加载。
从 Fragment 1.3.0-alpha04 开始,每个 FragmentManager 都会实现 FragmentResultOwner。这意味着 FragmentManager 可以充当 Fragment 结果的集中存储区。此更改通过设置 Fragment 结果并监听这些结果,而不要求 Fragment 直接引用彼此,让单独的 Fragment 相互通信。具体的实现可以查看这里。
在父级 Fragment 和子级 Fragment 之间传递结果,如需将结果从子级 Fragment 传递到父级 Fragment,父级 Fragment 在调用 setFragmentResultListener() 时应使用 getChildFragmentManager() 而不是 getParentFragmentManager()。同时也可以通过ChildFragment => ParentFragment进行传递。
通过共享ViewModel的形式也可以进行数据传递,或者通过EventBus,LiveDataBus等都可以。
FragmentPagerAdapter 中,即使fragment不可见了,他的视图可能会 destory(执行 onViewDestory,是否执行与setOffscreenPageLimit 方法设置的值有关),但是他的实例仍然会存在于内存中。当较多的fragment时, 会占用较大的内存。
FragmentSatePagerAdapter 中,当fragment不可见时,可能会将fragment的实例也销毁(执行 onDestory,是否执行与setOffscreenPageLimit 方法设置的值有关)。所以内存开销会小些, 适合多fragment的情形。
上面我们讲过setOffscreenPageLimit 方法设置的默认值是1。这个设置的值有两层含义:一是 ViewPager 会预加载几页;二是 ViewPager 会缓存 2n+1 页(n为设置的值)。
明白了setOffscreenPageLimit 方法的含义后就明白了在 ViewPager 中Fragment的生命周期了:在 FragmentPagerAdapter 中 setOffscreenPageLimit 的值影响的是 onViewDestory 方法。当缓存的 fragment 超过 setOffscreenPageLimit 设置的值后,那些 fragment 的onViewDestory 方法会回调;在 FragmentStatePagerAdapter 中,当缓存的 fragment 超过 setOffscreenPageLimit 设置的值后。那些 fragment 的onDestory 方法会回调。
总结起来就是:ViewPager 中的 fragment 是否执行 onViewDestory 或者 onDestory 与 setOffscreenPageLimit 方法设置的值有关。
FragmentContainerView、FragmentFactory,可以参考这篇文章,还有Google建议使用这些Fragment的新特性
参考文章
blog.csdn.net/u011151791/…
juejin.im/post/684490…
blog.csdn.net/tongsiw/art…
yuweiguocn.github.io/androidx-fr…
– EOF –