内容简介:大家都知道中只定义了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 的生命周期变化不处于同一个消息周期。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
火的礼物:人类与计算技术的终极博弈(第4版)
【美】Baase,Sara(莎拉芭氏) / 郭耀、李琦 / 电子工业出版社 / 89.00
《火的礼物:人类与计算技术的终极博弈 (第4版)》是一本讲解与计算技术相关的社会、法律和伦理问题的综合性读物。《火的礼物:人类与计算技术的终极博弈 (第4版)》以希腊神话中普罗米修斯送给人类的火的礼物作为类比,针对当前IT技术与互联网迅速发展带来的一些社会问题,从法律和道德的角度详细分析了计算机技术对隐私权、言论自由、知识产权与著作权、网络犯罪等方面带来的新的挑战和应对措施,讲解了计算技术对人类的......一起来看看 《火的礼物:人类与计算技术的终极博弈(第4版)》 这本书的介绍吧!