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;
image.gif
注意本文用的是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;
image.gif
所有的事务操作被封装为一个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;
}
}
image.gif
先来看一下事务添加的流程
image
首先是通过FragmentTransaction的add/replace/show/hide/remove/attach等操作,将这些操作(Op)添加到ArrayList<Op> mOps = new ArrayList<>()
中,后面进行commit的时候,FragmentTransaction是个抽象类,真正的实现类是BackStackRecord,然后进行commit,判断用户是否添加到回退栈中,然后将操作交给FragmentManagerImpl,进行事务的入队,开始处理事务集合,最终进行到moveToState方法,执行Fragment的生命周期的方法。
image
ArrayList<BackStackRecord> mBackStackIndices;
ArrayList<Integer> mAvailBackStackIndices; //可用的回退栈的索引
image.gif
这里要说一下这两个数组,只有加入到回退栈,才会进行管理。回退栈的添加需要手动调用具体方法。
一个是管理回退栈中的所有事务,一个是用于记录索引的。如上图所示,比如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;
}
image.gif
然后是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();
}
}
image.gif
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();
}
}
}
image.gif
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();
}
image.gif
然后是ensureExecReady方法
private void ensureExecReady(boolean allowStateLoss) {
···
mExecutingActions = true;
try {
executePostponedTransaction(null, null);
} finally {
mExecutingActions = false;
}
}
image.gif
最终执行到
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);
}
image.gif
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);
image.gif
预加载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中的生命周期进行了大规模的重构,已经和之前的版本有很大不同了,先来看下面这张图
image
图中展示了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;
}
image.gif
下面来看看懒加载的方案,我们可以发现FragmentPagerAdapter的构造方法过时了
public TabFragmentPagerAdapter(FragmentManager fm, List<Fragment> list) {
super(fm); //该方法过时了
this.mlist = list;
}
image.gif
可以看到内部又调用了两个参数放到方法,我们来看一下这个新的构造方法
public FragmentPagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
image.gif
多了一个int类型的参数
@Deprecated
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
image.gif
一个参数的构造方法默认传入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的新特性
作者:高工丶Adnroid
链接:https://www.jianshu.com/p/0a4b3825ff9f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。