1 简介
Fragment
是一个历史悠久的组件,从API 11
引入至今,已经成为Android
开发中最常用的组件之一。
Fragment
表示应用界面中可重复使用的一部分。Fragment
定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件。Fragment
不能独立存在,而是必须由Activity
或另一个Fragment
托管。Fragment
的视图层次结构会成为宿主的视图层次结构的一部分,或附加到宿主的视图层次结构。
本章节主要探索Fragment
的生命周期状态
及事务管理
。
2 Fragment生命周期
2.1 Fragment完整生命周期
onAttach -> onCreate -> onCreatedView -> onActivityCreated -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDestroy -> onDetach
如下图所示:
Fragment与Activity生命周期各状态的对比:
2.2 Fragment生命周期状态
Fragment
的生命周期状态只有5个,通过降序
以及升序
来进行判断。如果是升序,走显示的生命周期,降序为销毁的生命周期。由于Fragment的版本代码不断在更新,状态机也不断在变化,因此我们主要分析重点的状态机思路
INITIALIZED:Fragment 的一个新实例已实例化。
CREATED:系统已调用第一批 Fragment 生命周期方法。在 Fragment 处于此状态期间,系统也会创建与其关联的视图。
STARTED:Fragment 在屏幕上可见,但没有焦点,这意味着其无法响应用户输入。
RESUMED:Fragment 可见并已获得焦点。
DESTROYED:Fragment 对象已解除实例化。
FragmentManagerImpl
利用mCurState
成员变量来标记当前状态,Fragment
利用mState
成员变量来标记当前状态。
更新FragmentManagerImpl
的生命周期状态,这里以FragmentActivity
的生命周期回调开始,先看派发给FragmentManagerImpl
各个状态的时机。
FragmentManagerImpl
继承自FragmentManager
,FragmentActivity
通过生命周期调用Fragment
的生命周期,其实就是调用了FragmentManagerImpl
这个类来进行分发,最终都是走的dispatchStateChange()
方法进行状态机的更新。
FragmentActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
mFragments.dispatchCreate();
}
我们以FragmentActivity#onCreate()
方法为例,可以看到调用了mFragments
的dispatchCreate()
方法。然后调用FragmentManager的dispatchCreate()
方法。
FragmentManager.java
//(源码方法跳转太多,我直接帮你梳理出核心流程,跟你直接看源码会不同,但逻辑是相同的)
public void dispatchCreate() {
mStateSaved = false;
mStopped = false;
moveToState(Fragment.CREATED, false);
// 4、处理未执行的事务
execPendingActions();
}
void moveToState(int newState, boolean always) {
// 1、状态判断
if (nextState == mCurState) {
return;
}
mCurState = nextState;
// 2、执行添加的 Fragment
// Must add them in the proper order. mActive fragments may be out of order
for (int i = 0; i < mAdded.size(); i++) {
Fragment f = mAdded.get(i);
// 更新 Fragment 到当前状态
moveFragmentToExpectedState(f);
}
// 3、执行未添加,但是准备移除的 Fragment
// Now iterate through all active fragments. These will include those that are removed and detached.
for (int i = 0; i < mActive.size(); i++) {
Fragment f = mActive.valueAt(i);
if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {
// 更新 Fragment 到当前状态
moveFragmentToExpectedState(f);
}
}
}
其中,moveFragmentToExpectedState()
最终调用到moveToState(Fragment, int)
// moveFragmentToExpectedState 最终调用到
// 更新 Fragment 到当前状态
void moveToState(Fragment f, int newState) {
// 1、准备 Detatch Fragment 的情况,不再与宿主同步,进入 CREATED 状态
if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
newState = Fragment.CREATED;
}
// 2、移除 Fragment 的情况,Fragment 不再与宿主同步
if (f.mRemoving && newState > f.mState) {
if (f.isInBackStack()) {
// 2.1 移除动作添加到返回栈,则进入 CREATED 状态
newState = Math.min(nextState, Fragment.CREATED);
} else {
// 2.1 移除动作添加到返回栈,则进入 DESTROY 状态
newState = Math.min(nextState, Fragment.INITIALIZING);
}
}
// 3、真正执行状态转移
if (f.mState <= newState ) {
switch (f.mState) {
case Fragment.INITIALIZING:
if (nextState> Fragment.INITIALIZING) {
...
}
// fall through
case Fragment.CREATED:
...
// fall through
case Fragment.ACTIVITY_CREATED:
...
// fall through
case Fragment.STARTED:
...
}
} else {
switch (f.mState) {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {
...
}
// fall through
case Fragment.STARTED:
...
// fall through
case Fragment.ACTIVITY_CREATED:
...
// fall through
case Fragment.CREATED:
...
}
}
...
}
- 小伙伴们有没发现上面代码的特别之处?
case
里面没有break
。
这样的好处,是为了让Fragment
走完整的生命周期 - 触发状态转移时,首先会判断
Fragment
,如果已经处于目标状态newState
,则会跳过状态转移。然而,并不是FragmentManager
里所有的Fragment
都会执行状态转移,只有「mAdded为真&&mDetached为假」
的Fragment
才会更新到目标状态,其他Fragment
会脱离宿主状态。最后,状态转移完成后会处理未执行的事务execPendingActions()
;,可见每次dispatchXXX()
都会提供一次事务执行的窗口。
不同Fragment
标志位(Detach/Remove/返回栈)
与最终状态的关系总结如下表:
提示: 这些标志位可以通过事务进行干涉。
2.3 Fragment的生命周期对应状态
升序:
onCreate()
:Fragment已实例化并处于CREATED状态
。不过,其对应的视图尚未创建。
onCreateView()
:此方法可用于创建布局。Fragment已进入CREATED状态
。
onViewCreated()
:此方法在创建视图后调用。在此方法中,您通常会通过调用findViewById()将特定视图绑定到属性。
onStart()
:Fragment已进入STARTED状态
。
onResume()
:Fragment已进入RESUMED状态
,现已具有焦点(可响应用户输入)。
降序:
onPause()
:Fragment已重新进入STARTED状态
。相应界面对用户可见。
onStop()
:Fragment已重新进入CREATED状态
。该对象已实例化,但它在屏幕上不再显示。
onDestroyView()
:该方法在Fragment进入DESTROYED状态
之前调用。视图已从内存中移除,但Fragment对象仍然存在。
onDestroy()
:Fragment进入DESTROYED状态
。
下图总结了Fragment
生命周期以及状态之间的转换:
3 Fragment 事务管理
下面我们来了解下影响 Fragment 状态转移的第二个因素:事务。
3.1 事务概述
-
事务的特性是什么?
事务是恢复和并发的基本单位,具备4个基本特性:
原子性
:事务不可分割,要么全部完成,要么全部失败回滚;
一致性
:事务执行前后数据都具有一致性;
隔离性
:事务执行过程中,不受其他事务干扰;
持久性
:事务一旦完成,对数据的改变就是永久的。在Android
中体现为Fragment
状态保存后,commit()
提交事务会抛异常,因为这部分新提交的事务影响的状态无法保存。 -
事务的作用是什么?
使用事务FragmentTransaction
可以动态改变Fragment
状态,使得Fragment
在一定程度脱离宿主的状态。不过,事务依然受到宿主状态约束,例如:当前Activity
处于STARTED状态
,那么addFragment
不会使得Fragment
进入RESUME状态
。只有将来Activity
进入RESUME状态
时,才会同步Fragment
到最新状态。
3.2 不同事务操作的区别
add&remove
:Fragment状态在INITIALIZING
与RESUMED
之间转移;detach&attach
:Fragment状态在CREATE
与RESUMED
之间转移;replace
:先移除
所有containerId中的实例,再add
一个Fragment;show&hide
:只控制Fragment隐藏
或显示
,不会触发状态转移,也不会销毁Fragment视图或实例;hide&detach&remove
:hide
不会销毁视图和实例、detach
只销毁视图不销毁实例、remove
会销毁实例(自然也销毁视图)。不过,如果remove
的时候将事务添加到回退栈,那么Fragment实例就不会被销毁,只会销毁视图。
需要注意:detach
Fragment
并不会回调onDetach()
,而是转移到CREATE 状态
,回调onDetach()
需要转移到INITIALIZING
(是不是很奇葩的起名!)
3.3 不同事务提交方式
FragmentTransaction 定义了 5 种提交方式:
需要注意的地方:
onSaveInstanceState()
保存状态后,事务形成的新状态是不会被保存的。在状态保存之后调用commit()
或commitNow()
会抛异常,我们需要使用commitAllowingStateLoss()
和commitNowAllowingStateLoss()
进行提交,我们可以看下抛异常的具体代码:
FragmentManagerImpl.java
private void checkStateLoss() {
if (mStateSaved || mStopped) {
throw new IllegalStateException("Can not perform this action after onSaveInstanceState");
}
}
- 使用
commitNow()
或commitNowAllowingStateLoss()
提交的事务不允许加入回退栈
为什么有这个设计呢?可能是 Google 考虑到同时存在同步提交和异步提交的事务,并且两个事务都要加入回退栈时,无法确定哪个在上哪个在下是符合预期的,所以干脆禁止 commitNow()
加入回退栈(这里记住带Now的提交为同步提交,不加入回退栈中)。如果确实有需要同步执行+回退栈的应用场景,可以采用commit() + executePendingTransactions()
的取巧方法。相关源码体现如下:
BackStackRecord.java
@Override
public void commitNow() {
disallowAddToBackStack();
mManager.execSingleAction(this, false);
}
@Override
public void commitNowAllowingStateLoss() {
disallowAddToBackStack();
mManager.execSingleAction(this, true);
}
@NonNull
public FragmentTransaction disallowAddToBackStack() {
if (mAddToBackStack) {
throw new IllegalStateException("This transaction is already being added to the back stack");
}
mAllowAddToBackStack = false;
return this;
}
commitNow()
和executePendingTransactions()
都是同步执行,有区别吗?
commitNow()
是同步执行当前事务,而executePendingTransactions()
是同步执行事务队列中的全部事务。
4 Fragment逻辑流程
Fragment的逻辑流程本章节不做详细分析,贴上调用关系图供小伙伴们参考学习。
FragmentActivity到FragmentManager的相关调用关系图:
FragmentManager和BackStackRecord回退栈项的调用关系图:
5 总结
本章节我们主要了解Fragment的两大模块:
- 生命周期及状态 :
生命周期
和状态
密切相关,状态机升序
和降序
来实现生命周期
的对应。 - 事务管理:
commit()
、commitNow()
、commitAllowingStateLoss()
、commitNowAllowingStateLoss()
及executePendingTransactions()
的区别分析。