logo
当前位置:首 页 > 移动开发 >android > 查看文章

转自:伤心的猪大肠

https://juejin.cn/post/6892608279140204551

背景

本文基于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@Overridepublic 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是同步的,保证立即执行,但是不会加入到回退栈当中,因为可能会扰乱回退栈中内容的顺序,还有另外两个方法的详细内容可以看这篇文章。

@Overridepublic 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 */@NonNullpublic 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类型的参数

@Deprecatedpublic 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的新特性

参考文章

blog.csdn.net/u011151791/…

juejin.im/post/684490…

blog.csdn.net/tongsiw/art…

yuweiguocn.github.io/androidx-fr…

– EOF –

说说梦想,谈谈感悟 ,聊聊技术,有啥要说的来github留言吧 https://github.com/cjx2328

—— 陈 建鑫

陈建鑫
你可能也喜欢Related Posts
footer logo
未经许可请勿自行使用、转载、修改、复制、发行、出售、发表或以其它方式利用本网站之内容。站长联系:cjx2328#126.com(修改#为@)
Copyright ©ziao Studio All Rights Reserved. E-mail:cjx2328#126.com(#号改成@) 沪ICP备14052271号-3