文中的源代码基于Support包27.1.1版本
1 Fragment生命周期
大家都知道Fragment的生命周期,以及其对应的一些生命周期函数:
Fragment的生命周期函数很多,但其实Fragment中只定义了6种状态
1 | 复制代码static final int INITIALIZING = 0; // Not yet created. |
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这三个方法。
1 | 复制代码@SuppressWarnings("ReferenceEquality") |
通过上述源码可知,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方法,这些方法对应的生命周期我们已经讲过了就不在赘述了。
1 | 复制代码public void show(FragmentManager manager, String tag) { |
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
1 | 复制代码//FragmentManager.java |
onCreateView的工作基本上就是创建Fragment实例并将其迁移至指定状态了,我们以一个Activity正常启动的流程作为分析的场景,那么此时Fragment将最终进入CREATED状态。
在前面学习Fragment生命周期的时候,我们有提到过Activity进入onCreate之后会触发Fragment的onAttach和onCreate的生命周期回调。但在当前这种场景下,Fragment会提前触发onCreateView来创建视图,这一点可以在moveToState的源码中得到印证:
1 | 复制代码void moveToState(Fragment f, int newState, int transit, int transitionStyle, |
2.2 在代码中使用FragmentTransaction添加
此处我们以在Activity.onCreate方法中add一个Fragment作为分析场景
1 | 复制代码public class DemoActivity extends FragmentActivity{ |
先不管add里面进行了什么操作,我们知道如果不调用commit方法,那么add操作是不会起效的的。commit方法会经历以下调用链commit->commitInternal->FragmentManager.enqueueAction
1 | 复制代码//FragmentTransaction的实现类为BackStackRecord |
当mExecCommit被触发就会经历下面的调用链FragmentManager.execPendingActions->BackStackRecord.generateOps->
…->BackStackRecord.executeOps->FragmentManager.xxxFragment->FragmentManager.moveToState
最终发生了Fragment的状态迁移
那么mExecCommit是否真的就老老实实待在消息队列中等待被执行呢?答案是否定的。
我们来看看FragmentActivity.onStart方法
1 | 复制代码protected void onStart() { |
可以看到,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状态,但是经过测试,其生命变化与上图保持一致
本文转载自: 掘金