【大厂面试合集】大意了,字节面试被一道Fragment翻车了

本文详细解读了Fragment在Android中的生命周期顺序,重点讲解了Activity启动时Fragment的状态加载过程,以及宿主Activity如何影响Fragment状态变迁。还介绍了事务管理在Fragment控制中的作用,让读者理解如何通过事务灵活管理Fragment的生命周期回调。
摘要由CSDN通过智能技术生成

一道面试题

前段时间面试,面试官先问了一下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的状态转移

fragment有七个状态

static final int INVALID_STATE = -1;   // 为空时无效
static final int INITIALIZING = 0;     // 未创建
static final int CREATED = 1;          // 已创建,位于后台
static final int ACTIVITY_CREATED = 2; // Activity已经创建,Fragment位于后台
static final int STOPPED = 3;          // 创建完成,没有开始
static final int STARTED = 4;          // 开始运行,但是位于后台
static final int RESUMED = 5;          // 显示到前台

在这里有一个有意思的地方,STOPPED,我本来以为是停止阶段,但是在源码中写为” Fully created, not started.“,所以,其实Fragment的状态是对称的。RESUME状态反而是最后一个状态

调用过程如下

在这里插入图片描述
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 // 进入onCreate
D/MainFragment: onAttach: // 执行mFragments.dispatchCreate();
D/MainFragment: onCreate: 
D/MainActivity: onCreate: end // 退出onCreate

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

@Override
protected 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给我们的几种提交方式

  • commit()
    安排当前事务FragmentTransaction进行提交。但是提交后Fragment不会立即创建,而是由主线程异步来创建。也就是说使用commit()之后,你的Fragment不会被立即加入到Activity中。
    本次提交,必须在Activity的onSaveInstanceState调用之前提交。否则会抛异常。
  • commitAllowingStateLoss
    和commit类似。但是如果本次是在Activity的onSaveInstanceState调用之后,那么本次提交记录在Activity恢复的时候,可能不被保存。
  • commitNow()
    将事务立即提交。所有添加的Fragment会被立即初始化,并开始生命周期。所有被移除的Fragment将会被立即移除。
    调用这个方法,相当于调用commit,然后调用FragmentManager的executePendingTransactions()。
  • commitNowAllowingStateLoss()
    和commitNow类似。但是如果在在Activity的onSaveInstanceState调用之后,那么本次提交记录在Activity恢复的时候,可能不被保存。

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

@Override
public 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的生命周期方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李一恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值