文中的源代码基于Support包27.1.1版本java
你们都知道Fragment
的生命周期,以及其对应的一些生命周期函数:android
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个状态中流转,调用对应的生命周期方法而后进入下一个状态,以下图 markdown
Fragment
的生命周期与Activity
的生命周期密切相关 Activity
管理Fragment
生命周期的方式是在Activity
的生命周期方法中调用FragmentManager
的对应方法,经过FragmentManager
将现有的Fragment
迁移至下一个状态,同时触发相应的生命周期函数ide
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 |
上个图更加清晰:函数
咱们常用FragmentTransaction
中的add
、remove
、replace
、attach
、detach
、hide
、show
等方法对Fragment
进行操做,这些方法都会使Fragment
的状态发生变化,触发对应的生命周期函数布局
(假设此时Activity
处于RESUME
状态)post
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
状态。测试
经过FragmentPagerAdapter
咱们能够将Fragment
与ViewPager
结合起来使用,那么ViewPager
中的Fragment
的生命周期又是怎样的呢?this
其实也简单,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
的生命周期变化咱们能够经过上面这个例子获得。
在使用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 |
Fragment
的添加方式有两种:
FragmentTransaction
添加这里咱们就来聊聊,这两种不一样的添加方式对于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) { //... } //... } //... } 复制代码
此处咱们以在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
的生命周期变化不处于同一个消息周期。
咱们以一张图对本节内容进行总结:
28.0.0版本的support包中移除了
STOPPED
状态,可是通过测试,其生命变化与上图保持一致