文中的源代码基于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中的add、remove、replace、attach、detach、hide、show等方法对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操作会引起Fragment在INITIALIZING和RESUMED这两个状态之间迁移。
而attach/detach操作会引起Fragment在CREATED和RESUMED这两个状态之间迁移。
注:add函数这里有一个需要注意的点,如果当前Activity处于
STARTED状态,Fragment是无法进入RESUMED状态的,只有当Activity进入RESUME状态,然后触发onResume->FragmentManager.dispatchStateChange(Fragment.RESUMED),然后调用Fragment.onResume函数之后Fragment才会进入RESUMED状态。
1.3 Fragment与ViewPager
通过FragmentPagerAdapter我们可以将Fragment与ViewPager结合起来使用,那么ViewPager中的Fragment的生命周期又是怎样的呢?
其实也简单,FragmentPagerAdapter内部其实就是通过FragmentTransaction对Fragment进行操作的,主要涉及add、detach、attach这三个方法。
@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,后续通过attach和detach来操作。这些方法对应的生命周期我们可以参照上面的图即可。
我们举例来模拟一下看看,假设有ViewPager有5个页面,以及offscreenPageLimit为1,
- 第一次加载时,第一第二页通过
add函数被加载,处在RESUMED状态 - 滑动到第二页,第三页被加载,也是通过
add函数被加载的,处在RESUMED状态 - 继续滑动到第三页,此时第一页通过
detach函数被回收,处在CREATED状态,同时第四页通过add被加载处于RESUMED状态 - 滑动到第二页,此时第一页通过
attach被加载,处于RESUMED状态,第四页被detach处于CREATED状态
总结:ViewPager中当前页与当前页左右两页都处于RESUMED状态,其他页面要么未被创建,要么处于CREATED状态,滑动过程中Fragment的生命周期变化我们可以通过上面这个例子得到。
1.4 Fragment与DialogFragment
在使用DialogFragment的时候我们习惯使用它提供的show、hide方法进行显示或者隐藏。这两方法内部其实使用了FragmentTransaction的add、remove方法,这些方法对应的生命周期我们已经讲过了就不在赘述了。
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比较特别的是内部还维护了一个Dialog,DialogFragment设计之初就是使用FragmentManager来管理Dialog,主要使用了Dialog的show、hide、dismiss这三个方法。对应关系如下
| Fragment生命周期函数 | 对应的Dialog的方法 |
|---|---|
| onStart | show |
| onStop | hide |
| onDestoryView | dismiss |
2 不同的添加方式对Fragment的生命周期有什么影响
Fragment的添加方式有两种:
- 通过在xml文件中使用fragment标签添加
- 在代码中使用
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之后会触发Fragment的onAttach和onCreate的生命周期回调。但在当前这种场景下,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从消息队列中移除)。
FragmentActivity在onStart、onResume和onPostResume生命周期回调中都会调用FragmentManager.execPendingActions,因此当我们在Activity.onStart、Activity.onResume中通过代码添加Fragment时,Fragment的状态迁移分别会发生在Activity.onResume、Activity.onPostResume之后。
那么在onPostResume之后再添加Fragment会发生什么呢?
此时由于onPostResume方法中的FragmentManager.execPendingActions已经在super中调用过了,因此mExecCommit将会被触发,这里有一个最大的不同点就是Fragment的生命周期变化与Activity的生命周期变化不处于同一个消息周期。
2.3 总结
我们以一张图对本节内容进行总结:
28.0.0版本的support包中移除了
STOPPED状态,但是经过测试,其生命变化与上图保持一致