背景
本文基于androidx版本的Fragment
如果要是有面试官问你,Fragment是如何进行事务管理的?相信很多人可能都回答不出来,很多人可能觉得问题这个还不如问如何进行Fragment预/懒加载,生命周期,返回栈,状态保存等来的有水平。其实我觉得这个问题其实问的挺有深度,如果你想要了解Fragment的事务管理,需要对Fragment的源码进行充分了解,前面的那些问题都是小儿科了。
面试的时候问题有两种类型,一种是问典型,比如面试官问你HashMap,里面涉及到的方方面面非常多,可以进行大量扩展;还有一个就是直接问一定深度的问题,如果你能答上来,说明的掌握的到一定的程度,简单的问题就可以忽略了,答不上了我再问前面简单的问题。
要了解Fragment,首先需要了解它的生命周期,同时与Activity的生命周期的关联也是非常重要的。
Fragment的生命周期
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是如何进行事务管理的
先来看看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, @NonNull 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,提高数组的复用效率,之前的版本使用链表来管理的,但是效率上来讲还是不如这种方式来得高。
事务,一组操作全部要做,要么全部不干,流程绑在一起用,保证连续的操作是原子性的。
事务(FragmentTransaction)的四个commit的区别
commit() commitAllowingStateLoss() commitNow() commitNowAllowingStateLoss()
commit并不是立即执行的,它会被发送到主线程的任务队列当中,当主线程准备好执行它的时候执行。
//BackStackRecord.java
//commit
@Override
public int commit() {
return commitInternal(false);
}
//commitInternal
int 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是同步的,保证立即执行,但是不会加入到回退栈当中,因为可能会扰乱回退栈中内容的顺序,还有另外两个方法的详细内容可以看这篇文章。
@Override
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);
}
Fragment状态的保存和恢复
状态保存
当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的生命周期。
fragment的预加载和懒加载
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懒加载
所谓的懒加载,就是结合生命周期判断fragment是否为可见状态,根据可见状态判断是否加载数据。
普通的Fragment(非AndroidX)
使用setUserVisibleHint或者onHiddenChanged进行辅助判断,show和hide的时候不会调用生命周期的方法,而是会调用onHiddenChanged,详细的设置可以参考该文章
androidx懒加载
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
*/
@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
@NonNull 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类型的参数
@Deprecated
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加载数据的逻辑放到onResume()方法中,这样就保证了Fragment可见时才会加载数据。
- 声明一个变量标记是否是首次执行onResume()方法,因为每次Fragment由不可见变为可见都会执行onResume()方法,需要防止数据的重复加载。此外,如果我们使用的是FragmentPagerAdapter,切换导致Fragment被销毁时是不会执行onDestory()和onDetach()方法的,只会执行到onDestroyView()方法,因此在onDestroyView()方法中我们还需要将这个变量重置,否则当Fragment再次可见时就不会重新加载数据了。
Fragment之间传递数据
从 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, FragmentStatePagerAdapter 的区别
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 方法设置的值有关。
androidx的中一些新特性
FragmentContainerView、FragmentFactory,可以参考这篇文章,还有Google建议使用这些Fragment的新特性
参考文章