写点什么

翻车了,字节一道 Fragment 面试题

用户头像
小松漫步
关注
发布于: 刚刚

一道面试题

前段时间面试,面试官先问了一下 fragment 的生命周期,我一看这简单呀,直接按照下图回答



面试官点点头,然后问,如果 Activity 里面有一个 fragment,那么启动他们时,他们的生命周期加载顺序是什么?



所以今天,我们好好了解了解这个用得非常多,但是对底层不是很理解的 fragment 吧


首先回答面试官的问题,Fragment 的 start 与 activity 的 start 的调用时机


调用顺序:

D/MainActivity: MainActivity:

D/MainActivity: onCreate: start

D/MainFragment: onAttach:

D/MainFragment: onCreate:


D/MainActivity: onCreate: end

D/MainFragment: onCreateView:

D/MainFragment: onViewCreated:

D/MainFragment: onActivityCreated:

D/MainFragment: onViewStateRestored:

D/MainFragment: onCreateAnimation:

D/MainFragment: onCreateAnimator:

D/MainFragment: onStart:


D/MainActivity: onStart:

D/MainActivity: onResume:

D/MainFragment: onResume:


可以看到 Activity 在 oncreate 开始时,Fragment 紧接着 attach,create,然后 activity 执行完毕 onCreate 方法


此后都是 Fragment 在执行,直到 onStart 方法结束


然后轮到 Activity,执行 onStart onResume


也就是,Activity 创建的时候,Fragment 一同创建,同时 Fragment 优先在后台先展示好,最后 Activity 带着 Fragment 一起展示到前台。

是什么?

Fragment 中文翻译为”碎片“,在手机中,每一个 Activity 作为一个页面,有时候太大了,尤其是在平板的横屏下,我们希望左半边是一根独立模块,右半边是一个独立模块,比如一个新闻 app,左边是标题栏,右边是显示内容


此时就非常适合 Fragment


Fragment 是内嵌入 Activity 中的,可以在 onCreateView 中加载自定义的布局,使用 LayoutInflater,然后 Activity 持有 FragmentManager 对 Fragment 进行控制,下图是他的代码框架



我们的 Activity 一般是用 AppCompatActivity,而 AppCompatActivity 继承了 FragmentActivity


public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,        TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
复制代码


也就是说 Activity 之所支持 fragment,是因为有 FragmentActivity,他内部有一个 FragmentController,这个 controller 持有一个 FragmentManager,真正做事的就是这个 FragmentManager 的实现类 FragmentManagerImpl

整体架构

回到我们刚才的面试题,关于生命周期绝对是重中之重,但是实际上,生命周期本质只是被其他地方的方法被动调用而已,关键是 Fragment 自己的状态变化了,才会回调生命周期方法,所以我们来看看 fragment 的状态转移


static final int INITIALIZING = 0;     初始状态,Fragment 未创建static final int CREATED = 1;          已创建状态,Fragment 视图未创建static final int ACTIVITY_CREATED = 2; 已视图创建状态,Fragment 不可见static final int STARTED = 3;          可见状态,Fragment 不处于前台static final int RESUMED = 4;          前台状态,可接受用户交互
复制代码


fragment 有五个状态,


调用过程如下



Fragment 的状态转移过程主要受到宿主,事务的影响,宿主一般就是 Activity,在我们刚刚的题目中,看到了 Activity 与 Fragment 的生命周期交替执行,本质上就是,Activity 执行完后通知了 Fragment 进行状态转移,而 Fragment 执行了状态转移后对应的回调了生命周期方法


下图可以更加清晰


宿主改变 Fragment 状态

那么我们不禁要问,Activity 如何改变 Fragment 的状态?


我们知道 Activity 继承于 FragmentActivity,最终是通过持有的 FragmentManager 来控制 Fragment,我们去看看


FragmentActivity   @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState); ... mFragments.dispatchCreate();}
复制代码


可以看到,onCreate 方法中执行了mFragments.dispatchCreate();,看起来像是通知 Fragment 的 onCreate 执行,这也印证了我们开始时的周期回调顺序


D/MainActivity: MainActivity: D/MainActivity: onCreate: start // 进入onCreateD/MainFragment: onAttach: // 执行mFragments.dispatchCreate();D/MainFragment: onCreate: D/MainActivity: onCreate: end // 退出onCreate
复制代码


类似的 FragmentActivity 在每一个生命周期方法中都做了相同的事情


@Overrideprotected void onDestroy() {    super.onDestroy();
if (mViewModelStore != null && !isChangingConfigurations()) { mViewModelStore.clear(); }
mFragments.dispatchDestroy();}
复制代码


我们进入 dispatchCreate 看看,


Runnable mExecCommit = new Runnable() {    @Override    public void run() {        execPendingActions();    }
//内部修改了两个状态public void dispatchCreate() { mStateSaved = false; mStopped = false; dispatchStateChange(Fragment.CREATED);
private void dispatchStateChange(int nextState) { try { mExecutingActions = true; moveToState(nextState, false);// 转移到nextState } finally { mExecutingActions = false; } execPendingActions();}//一路下来会执行到void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { // Fragments that are not currently added will sit in the onCreate() state. if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) { newState = Fragment.CREATED; } if (f.mRemoving && newState > f.mState) { if (f.mState == Fragment.INITIALIZING && f.isInBackStack()) { // Allow the fragment to be created so that it can be saved later. newState = Fragment.CREATED; } else { // While removing a fragment, we can't change it to a higher state. newState = f.mState; } } ...}
复制代码


可以看到上面的代码,最终执行到 moveToState,通过判断 Fragment 当前的状态,同时 newState > f.mState,避免状态回退,然后进行状态转移


状态转移完成后就会触发对应的生命周期回调方法

事务管理

如果 Fragment 只能随着 Activity 的生命周期变化而变化,那就太不灵活了,所以 Android 给我们提供了一个独立的操作方案,事务


同样由 FragManager 管理,具体由 FragmentTransaction 执行,主要是添加删除替换 Fragment 等,执行操作后,需要提交来保证生效


FragmentManager fragmentManager = ...FragmentTransaction transaction = fragmentManager.beginTransaction();transaction.setReorderingAllowed(true);
transaction.replace(R.id.fragment_container, ExampleFragment.class, null); // 替换Fragment
transaction.commit();// 这里的commit是提交的一种方法
复制代码


Android 给我们的几种提交方式



FragmentTransaction 是个挂名抽象类,真正的实现在 BackStackState 回退栈中,我们看下 commit


@Overridepublic int commit() {    return commitInternal(false);}int commitInternal(boolean allowStateLoss) {    if (mCommitted) throw new IllegalStateException("commit already called");    ...    mCommitted = true;    if (mAddToBackStack) {        mIndex = mManager.allocBackStackIndex(this);//1    } else {        mIndex = -1;    }    // 入队操作    mManager.enqueueAction(this, allowStateLoss);//2    return mIndex;}
复制代码


可以看到,commit 的本质就是将事务提交到队列中,这里出现了两个数组,注释 1 处


ArrayList<BackStackRecord> mBackStackIndices;ArrayList<Integer> mAvailBackStackIndices;public int allocBackStackIndex(BackStackRecord bse) {    synchronized (this) {        if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {            if (mBackStackIndices == null) {                mBackStackIndices = new ArrayList<BackStackRecord>();            }            int index = mBackStackIndices.size();            mBackStackIndices.add(bse);            return ind        } else {            int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);            mBackStackIndices.set(index, bse);            return index;        }    }}
复制代码


mBackStackIndices 数组,每个元素是一个回退栈,用来记录索引。比如说,当有五个 BackStackState 时,移除掉 1,3 两个,就是在 mBackStackIndices 将对应元素置为 null,然后 mAvailBackStackIndices 会添加这两个回退栈,记录被移除的回退栈


当下次 commit 时,就判定 mAvailBackStackIndices 中的索引,对应的 BackStackState 一定是 null,直接写到这个索引即可


而一组操作都 commit 到同一个队列里面,所以要么全部完成,要么全部不做,可以保证原子性


注释二处是一个入队操作


public void enqueueAction(OpGenerator action, boolean allowStateLoss    synchronized (this) {        ...        mPendingActions.add(action);        scheduleCommit(); // 真正的提交    }}public void scheduleCommit() {    synchronized (this) {        boolean postponeReady =                mPostponedTransactions != null && !mPostponedTransactions.isEmpty();        boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;        if (postponeReady || pendingReady) {            mHost.getHandler().removeCallbacks(mExecCommit);            mHost.getHandler().post(mExecCommit); // 发送请求        }}
复制代码


这里最后 mHost.getHandler()是拿到了宿主 Activity 的 handler,使得可以在主线程执行,mExecCommit 本身是一个线程


我们继续看下这个 mExecCommit


Runnable mExecCommit = new Runnable() {    @Override    public void run() {        execPendingActions();    }};public boolean execPendingActions() {    ensureExecReady(true);      ...    doPendingDeferredStart();    burpActive();    return didSomething;}void doPendingDeferredStart() {    if (mHavePendingDeferredStart) {        mHavePendingDeferredStart = false;        startPendingDeferredFragments();    }}void startPendingDeferredFragments() {    if (mActive == null) return;    for (int i=0; i<mActive.size(); i++) {        Fragment f = mActive.valueAt(i);        if (f != null) {            performPendingDeferredStart(f);        }    }}public void performPendingDeferredStart(Fragment f) {    if (f.mDeferStart) {        f.mDeferStart = false;        moveToState(f, mCurState, 0, 0, false); // 最终到了MoveToState    }}
复制代码


还记得我们在宿主改变 Fragment 状态,里面的最终路径吗?是的,就是这个 moveToState,无论是宿主改变 Fragment 状态,还是事务来改变,最终都会执行到 moveToState,然后 call 对应的生命周期方法来执行,这也是为什么我们要将状态转移作为学习主线,而不是生命周期。


除了 commit,可以看到 FragmentTransaction 有众多对 Fragment 进行增删改查的方法



都是由 BackStackState 来执行,最后都会执行到 moveToState 中


具体是如何改变的,有很多细节,这里不再赘述。

小结

本节我们讲了 Fragment 在 android 系统中的状态,那就是通过自身状态转移来回调对应生命周期方法,这块是自动实现的,我们开发时不太需要关注状态转移,只要知道什么时候执行某个生命周期方法,然后再在对应方法中写业务逻辑即可


有两个方法可以让 Fragment 状态转移,


  • 宿主 Activity 生命周期内自动修改 Fragment 状态,回调 Fragment 的生命周期方法

  • 通过手动提交事务,修改 Fragment 状态,回调 Fragment 的生命周期方法


参考资料


https://juejin.cn/post/6844904001935261710#heading-8https://juejin.cn/post/6984605769245130788#heading-15https://juejin.cn/post/6970998913754988552#heading-16

发布于: 刚刚阅读数: 2
用户头像

小松漫步

关注

公众号【小松漫步】,累过哭过,依然坚持 2018.12.12 加入

98年刚入职鹅厂的程序员

评论

发布
暂无评论
翻车了,字节一道 Fragment面试题