文中的源代码基于 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 状态, 但是经过测试, 其生命变化与上图保持一致
来源: https://juejin.im/post/5c2df25be51d451d46035449