背景
本文基于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的新特性
作者
参考文章
网友评论