Fragment生命周期

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

1 Fragment生命周期

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

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

1
2
3
4
5
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这三个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
复制代码@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方法,这些方法对应的生命周期我们已经讲过了就不在赘述了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
复制代码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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
复制代码//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的源码中得到印证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
复制代码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作为分析场景

1
2
3
4
5
6
7
8
9
复制代码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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
复制代码//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方法

1
2
3
4
5
6
7
8
9
10
11
12
复制代码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状态,但是经过测试,其生命变化与上图保持一致

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%