Fragment生命周期

20,239 阅读8分钟

文中的源代码基于Support包27.1.1版本

1 Fragment生命周期

大家都知道Fragment的生命周期,以及其对应的一些生命周期函数:

Fragment的生命周期函数很多,但其实Fragment中只定义了6种状态

static final int INITIALIZING = 0;     // Not yet created.
static final int CREATED = 1;          // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3;          // Fully created, not started.
static final int STARTED = 4;          // Created and started, not resumed.
static final int RESUMED = 5;          // Created started and resumed.

Fragment的整个生命周期一直在这6个状态中流转,调用对应的生命周期方法然后进入下一个状态,如下图

1.1 Fragment与Activity

Fragment的生命周期与Activity的生命周期密切相关 Activity管理Fragment生命周期的方式是在Activity的生命周期方法中调用FragmentManager的对应方法,通过FragmentManager将现有的Fragment迁移至下一个状态,同时触发相应的生命周期函数

Activity生命周期函数 FragmentManager触发的函数 Fragment状态迁移 Fragment生命周期回调
onCreate dispatchCreate INITIALIZING->
CREATED
onAttach、onCreate
onStart dispatchStart CREATED->
ACTIVITY_CREATED->
STOPPED->
STARTED
onCreateView、onActivityCreated、onStart
onResume(准确来讲是onPostResume) dispatchResume STARTED->
RESUMED
onResume
onPause dispatchPause RESUMED->
STARTED
onPause
onStop dispatchStop STARTED->
STOPPED
onStop
onDestroy dispatchDestroy STOPPED->
ACTIVITY_CREATED->
CREATED->
INITIALIZING
onDestroyView、onDestroy、onDetach

上个图更加清晰:

1.2 Fragment与FragmentTransaction

我们经常使用FragmentTransaction中的addremovereplaceattachdetachhideshow等方法对Fragment进行操作,这些方法都会使Fragment的状态发生变化,触发对应的生命周期函数

(假设此时Activity处于RESUME状态)

FragmentTransaction中的方法 Fragment触发的生命周期函数
add onAttach->
onCreate->
onCreateView->
onActivityCreated->
onStart->
onResume
remove onPause->
onStop->
onDestoryView->
onDestory->
onDetach
replace replace可拆分为add和remove,
detach (在调用detach之前需要先通过add添加Fragment)
onPause->
onStop->
onDestoryView
attach (调用attach之前需要先调用detach)
onCreateView->
onActivityCreated->
onStarted->
onResumed
hide 不会触发任何生命周期函数
show 不会触发任何生命周期函数

通过对Fragment生命周期的变化的观察,我们可以很容易发现,add/remove操作会引起FragmentINITIALIZINGRESUMED这两个状态之间迁移。 而attach/detach操作会引起FragmentCREATEDRESUMED这两个状态之间迁移。

注:add函数这里有一个需要注意的点,如果当前Activity处于STARTED状态,Fragment是无法进入RESUMED状态的,只有当Activity进入RESUME状态,然后触发onResume->FragmentManager.dispatchStateChange(Fragment.RESUMED),然后调用Fragment.onResume函数之后Fragment才会进入RESUMED状态。

1.3 Fragment与ViewPager

通过FragmentPagerAdapter我们可以将FragmentViewPager结合起来使用,那么ViewPager中的Fragment的生命周期又是怎样的呢?

其实也简单,FragmentPagerAdapter内部其实就是通过FragmentTransactionFragment进行操作的,主要涉及adddetachattach这三个方法。

@SuppressWarnings("ReferenceEquality")
@Override
public Object instantiateItem(ViewGroup container, int position) {
    //...
    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        //如果已经存在Fragment实例
        //那么使用attach操作进行添加
        mCurTransaction.attach(fragment);
    } else {
        //Fragment实例还没创建,通过getItem创建一个实例
        //然后通过add操作添加
        fragment = getItem(position);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }
    //...
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    //...
    //使用detach销毁Fragment
    mCurTransaction.detach((Fragment)object);
}

通过上述源码可知,FragmentPagerAdapter通过FragmentTransaction.add方法添加Fragment,后续通过attachdetach来操作。这些方法对应的生命周期我们可以参照上面的图即可。 我们举例来模拟一下看看,假设有ViewPager有5个页面,以及offscreenPageLimit为1,

  1. 第一次加载时,第一第二页通过add函数被加载,处在RESUMED状态
  2. 滑动到第二页,第三页被加载,也是通过add函数被加载的,处在RESUMED状态
  3. 继续滑动到第三页,此时第一页通过detach函数被回收,处在CREATED状态,同时第四页通过add被加载处于RESUMED状态
  4. 滑动到第二页,此时第一页通过attach被加载,处于RESUMED状态,第四页被detach处于CREATED状态

总结:ViewPager中当前页与当前页左右两页都处于RESUMED状态,其他页面要么未被创建,要么处于CREATED状态,滑动过程中Fragment的生命周期变化我们可以通过上面这个例子得到。

1.4 Fragment与DialogFragment

在使用DialogFragment的时候我们习惯使用它提供的showhide方法进行显示或者隐藏。这两方法内部其实使用了FragmentTransactionaddremove方法,这些方法对应的生命周期我们已经讲过了就不在赘述了。

public void show(FragmentManager manager, String tag) {
    mDismissed = false;
    mShownByMe = true;
    FragmentTransaction ft = manager.beginTransaction();
    //核心操作
    ft.add(this, tag);
    ft.commit();
}


void dismissInternal(boolean allowStateLoss) {
    //...
    if (mBackStackId >= 0) {
        //...
    } else {
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        //核心操作
        ft.remove(this);
        if (allowStateLoss) {
            ft.commitAllowingStateLoss();
        } else {
            ft.commit();
        }
    }
}

DialogFragment比较特别的是内部还维护了一个DialogDialogFragment设计之初就是使用FragmentManager来管理Dialog,主要使用了Dialogshowhidedismiss这三个方法。对应关系如下

Fragment生命周期函数 对应的Dialog的方法
onStart show
onStop hide
onDestoryView dismiss

2 不同的添加方式对Fragment的生命周期有什么影响

Fragment的添加方式有两种:

  1. 通过在xml文件中使用fragment标签添加
  2. 在代码中使用FragmentTransaction添加

这里我们就来聊聊,这两种不同的添加方式对于Fragment的生命周期回调会产生什么样的影响。

2.1 使用fragment标签添加

xml中的Fragment的实例创建最终会交由FragmentManager负责,方法为onCreateView

//FragmentManager.java
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    //判断是否是Fragment标签
    if (!"fragment".equals(name)) {
        return null;
    }

    //下面这些代码是获取xml中定义的
    //Fragment的一些信息
    //如类名(全路径)、id、tag
    String fname = attrs.getAttributeValue(null, "class");
    TypedArray a =  context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
    if (fname == null) {
        fname = a.getString(FragmentTag.Fragment_name);
    }
    int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
    String tag = a.getString(FragmentTag.Fragment_tag);
    a.recycle();

    //检查指定的Fragment类是否派生子Fragment
    if (!Fragment.isSupportFragmentClass(mHost.getContext(), fname)) {
        return null;
    }
    
    //必须满足id不为空或者tag不为空或者包裹Fragment的Container的id不为空
    //否则抛出异常
    int containerId = parent != null ? parent.getId() : 0;
    if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
        throw new IllegalArgumentException(attrs.getPositionDescription()
                + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
    }

    // If we restored from a previous state, we may already have
    // instantiated this fragment from the state and should use
    // that instance instead of making a new one.
    Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
    if (fragment == null && tag != null) {
        fragment = findFragmentByTag(tag);
    }
    if (fragment == null && containerId != View.NO_ID) {
        fragment = findFragmentById(containerId);
    }

    //log...
    
    //通过反射创建Fragment实例
    if (fragment == null) {
        fragment = Fragment.instantiate(context, fname);
        //这个字段标志该Fragment实例是来自于xml文件
        fragment.mFromLayout = true;
        fragment.mFragmentId = id != 0 ? id : containerId;
        fragment.mContainerId = containerId;
        fragment.mTag = tag;
        fragment.mInLayout = true;
        fragment.mFragmentManager = this;
        fragment.mHost = mHost;
        fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
        //重点方法
        //第二个参数名为moveToStateNow
        //此处为true,因此该Fragment将会立即
        //迁移到当前FragmentManager所记录的状态
        //通常我们在onCreate方法中设置layout
        //因此通常来讲此时FragmentManager
        //处于CREATED状态
        addFragment(fragment, true);

    } else if (fragment.mInLayout) {
        //...
    } else {
        //...
    }

    if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
        //如果当前FragmentManager处于INITIALIZING状态
        //那么强制将该Fragment迁移至CREATED状态
        moveToState(fragment, Fragment.CREATED, 0, 0, false);
    } else {
        //如果此时FragmentManager的状态大于CREATED
        //那么将该Fragment迁移至对应的状态
        moveToState(fragment);
    }

    //...
    return fragment.mView;
}

onCreateView的工作基本上就是创建Fragment实例并将其迁移至指定状态了,我们以一个Activity正常启动的流程作为分析的场景,那么此时Fragment将最终进入CREATED状态。

在前面学习Fragment生命周期的时候,我们有提到过Activity进入onCreate之后会触发FragmentonAttachonCreate的生命周期回调。但在当前这种场景下,Fragment会提前触发onCreateView来创建视图,这一点可以在moveToState的源码中得到印证:

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
            
    //...
     switch (f.mState) {
        case Fragment.INITIALIZING:
            //...
        case Fragment.CREATED:
            //...
            //下面这个if语句来自于ensureInflatedFragmentView方法
            //为了方便,这里直接贴上了该方法的代码
            //如果该Fragment来自于布局文件
            //那么触发onCreateView创建试图实例
             if (f.mFromLayout && !f.mPerformedCreateView) {
                f.mView = f.performCreateView(f.performGetLayoutInflater(
                        f.mSavedFragmentState), null, f.mSavedFragmentState);
                if (f.mView != null) {
                    f.mInnerView = f.mView;
                    f.mView.setSaveFromParentEnabled(false);
                    if (f.mHidden) f.mView.setVisibility(View.GONE);
                    f.onViewCreated(f.mView, f.mSavedFragmentState);
                    dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false);
                } else {
                    f.mInnerView = null;
                }
            }
            if (newState > Fragment.CREATED) {
                //...
            }
        //...
     }
    //...

}

2.2 在代码中使用FragmentTransaction添加

此处我们以在Activity.onCreate方法中add一个Fragment作为分析场景

public class DemoActivity extends FragmentActivity{
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.demo);
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(R.id.container, new DemoFragment());
        ft.commit();
    }
}

先不管add里面进行了什么操作,我们知道如果不调用commit方法,那么add操作是不会起效的的。 commit方法会经历以下调用链 commit-> commitInternal-> FragmentManager.enqueueAction

//FragmentTransaction的实现类为BackStackRecord
//action的实际类型是BackStackRecord
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        synchronized (this) {
            //...
            mPendingActions.add(action);
            synchronized (this) {
                boolean postponeReady =
                        mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
                boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
                if (postponeReady || pendingReady) {
                    //重点
                    //getHandler拿到的是一个主线程的Handler
                    //这里没有直接调用moveToState,而是抛了一个
                    //消息至消息队列,这将导致Fragment的状态迁移被延后
                    mHost.getHandler().removeCallbacks(mExecCommit);
                    mHost.getHandler().post(mExecCommit);
                }
            }
        }
    }

mExecCommit被触发就会经历下面的调用链 FragmentManager.execPendingActions-> BackStackRecord.generateOps-> ...-> BackStackRecord.executeOps-> FragmentManager.xxxFragment-> FragmentManager.moveToState 最终发生了Fragment的状态迁移

那么mExecCommit是否真的就老老实实待在消息队列中等待被执行呢?答案是否定的。 我们来看看FragmentActivity.onStart方法

protected void onStart() {
    super.onStart();
    //...
    
    //敲黑板
    mFragments.execPendingActions();

    //...

    mFragments.dispatchStart();
    //...
}

可以看到,execPendingActions被提前触发了,再搭配下面的dispatchStart,那么Fragment将从INITIALIZING一下子迁移至STARTED(execPendingActions方法触发后会将mExecCommit从消息队列中移除)。 FragmentActivityonStartonResumeonPostResume生命周期回调中都会调用FragmentManager.execPendingActions,因此当我们在Activity.onStartActivity.onResume中通过代码添加Fragment时,Fragment的状态迁移分别会发生在Activity.onResumeActivity.onPostResume之后。 那么在onPostResume之后再添加Fragment会发生什么呢? 此时由于onPostResume方法中的FragmentManager.execPendingActions已经在super中调用过了,因此mExecCommit将会被触发,这里有一个最大的不同点就是Fragment的生命周期变化与Activity的生命周期变化不处于同一个消息周期。

2.3 总结

我们以一张图对本节内容进行总结:

28.0.0版本的support包中移除了STOPPED状态,但是经过测试,其生命变化与上图保持一致