内容简介:大家都知道中只定义了6种状态
大家都知道 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
的添加方式有两种:
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
的生命周期变化不处于同一个消息周期。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。