概述
Fragment在开发中经常使用,它的出现大大解放了Activity,使得原来在Activity中书写的代码,可以放到Fragment中,并且方便复用。之前虽然经常使用Fragment但是并没有深究其内部是如何实现的,遇到使用上的一些问题也没有深究问题发生的根本原因,借此机会对Fragment进行深入研究下,涉及到的点如下
- Fragment的生命周期是如何调用的
- Fragment是如何存储与恢复的
- Fragment重影以及在
ViewPager
中使用需要注意的地方 - Fragment使用的一些技巧
Fragment
源码版本为androidx:1.1.0
Fragment是什么
Fragment包含一个View对象,可以在Activity的布局中添加或删除,它还有生命周期、回退栈、销毁重建机制等,它的这些特性使它成为一个微型的Activity
,同时相比Activity
来说,
优点有:
- 界面切换时不需要进程间通信,很快
- 复用很方便
缺点有:
- 使用复杂,生命周期比
Activity
多,Fragment
中的View
的和Fragment
的生命周期不一样 - 重建机制导致容易使用出错
- 如果在转场动画过程中发生了数据渲染,那么就会产生动画卡顿现象,因为数据渲染造成了 UI 重新 layout
Fragment的生命周期
平常使用中发现,Fragment
和Activity
的生命周期调用基本一致,不难猜想到,Fragment
被Activity
持有,并且Activity
在各个生命周期方法里调用Fragment
相应的生命周期,查看FragmentActivity
里代码如下:
//FragmentActivity.java
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.dispatchCreate(); //分发onCreate给Fragment
}
@Override
protected void onStart() {
mFragments.dispatchStart(); //分发onStart给Fragment
}
... // 其他生命周期方法是类似的
Activity
将Fragment
的相关的操作放在FragmentController
中,在Activity
生命周期调用时,通过FragmentController
来给Fragment
分发生命周期事件,我们通过以下常用的方式来深入探索,我们常用的方式如下:
getSupportFragmentManager()
.beginTransaction()
.add(R.id.container,new FragmentTest())
.commit();
这里引申出两个相关的类
- FragmentManager
- FragmentTransaction
FragmentManager
getSupportFragmentManager()
得到一个FragmentManager
对象,它是用来管理Fragment
,包含所有添加到当前FragmentManager
中的所有Fragment
、Fragment
的回退栈,调用Fragment
生命周期等等,不过它是一个抽象类,它的实现是FragmentManagerImpl
,关注它内部几个有用的成员变量
//通过addFragment添加或者从布局<fragment/>添加而来
final ArrayList<Fragment> mAdded = new ArrayList<>();
//fragment回退栈
ArrayList<BackStackRecord> mBackStack;
FragmentTransaction
getSupportFragmentManager() .beginTransaction()
获取到的就是一个FragmentTransaction
,它代表一个事务,表示要执行的一系列动作要么都完成,要么都不完成,在Fragment
中使用的意义如下:
getSupportFragmentManager()
.beginTransaction()
.add()
.add()
.remove()
.addToBackStack("")
.commit();
我们是可以在一次提交中同时add
、remove
多次,而这一系列操作就是一个事务,上面代码提交时加入了回退栈,此时点击手机上的返回键,将会执行相反的操作,既remove
、remove
、add
这三个操作,这就提现了事务的特点,事务是指一组原子性的操作,这些操作是不可分割的整体,要么全完成,要么全不完成,完成后可以回滚到完成前的状态
FragmentTransaction
也是一个抽象类的,它的实现是BackStackRecord
,它就是我们在上面看到的回退栈所保存的元素。其中有一个关键的变量
ArrayList<Op> mOps = new ArrayList<>();
static final class Op {
int mCmd;
Fragment mFragment;
}
mOps
这个列表保存的就是一次事物中的所有动作,mCmd
表示我们对Fragment
的操作,比如add
、remove
等,当commit()
之后在某个时间点,遍历mOps
完成响应操作,这就是事务实现的机制。
提交事务我们上面使用的是commit
,它还有一些相似的方法
commit()
: 通过Handler来提交,并不是立即执行。如果在onStop
或者onSaveInstanceState
之后提交会抛出异常throw new IllegalStateException( "Can not perform this action after onSaveInstanceState");
commitAllowingStateLoss()
: 和commit()
的不同之处在与onStop或者onSaveInstanceState之后提交不抛出异常可以正常执行commitNow()
: 相比与commit()
不同点在于是立即执行提交而不是通过HandlercommitNowAllowingStateLoss()
: 类似
生命周期回调
FragmentTransaction
只是对事务的记录,最终执行还是调用FragmentManager
的相关方法来进行add
、remove
、show
、hide
等操作。以add
为例,commit()
之后,通过一系列方法后,调用FragmentManager
的executeOps()
将Fragment
放入上面提到的mAdded
列表中,然后调用到moveToState()
来处理Fragment
相关的生命周期,在介绍moveToState()
之前先看下
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // Fully created, not started.
static final int STARTED = 3; // Created and started, not resumed.
static final int RESUMED = 4; // Created started and resumed.
这些是标记Fragment
当前的状态,这里有一个令人疑惑的点就是,为什么状态只到resume
,而答案就在moveToState
中,
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
// newState 是FragmentManager的状态,是跟随Activity的
if (f.mState <= newState) {
switch (f.mState) {
case Fragment.INITIALIZING:
if (newState > Fragment.INITIALIZING) {
f.performAttach();
if (!f.mIsCreated) {
f.performCreate(f.mSavedFragmentState);
}
}
case Fragment.CREATED:
if (newState > Fragment.CREATED) {
ViewGroup container = null;
f.mContainer = container;
f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
if (container != null) {
container.addView(f.mView);
}
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
f.performActivityCreated(f.mSavedFragmentState);
if (f.mView != null) {
f.restoreViewState(f.mSavedFragmentState);
}
}
case Fragment.ACTIVITY_CREATED:
if (newState > Fragment.ACTIVITY_CREATED) {
f.performStart();
}
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
f.performResume();
}
}
}
}
根据Fragment
当前的状态与newState
比较,来判断应该到达什么生命状态,注意上述case
中是没有break
的,所以Fragment
会在到达它的目标状态过程中,会调用相应的生命周期方法,最终达到目标状态。我们也看到在onCreateView
调用之后,就把该方法创建的View
添加到了包含Fragment
的容器中。以上只是到onResume
状态,还有一部分代码没贴出来,来看一下
// newState 是FragmentManager的状态,是跟随Activity的
else if (f.mState > newState) {
switch (f.mState) {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {
f.performPause();
}
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
f.performStop();
}
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {
if (f.mView != null) {
if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) {
saveFragmentViewState(f);
}
}
f.performDestroyView();
if (f.mView != null && f.mContainer != null) {
if (f.getParentFragment() == null || !f.getParentFragment().mRemoving) {
f.mContainer.removeView(f.mView);
}
}
f.mContainer = null;
f.mView = null;
f.mViewLifecycleOwner = null;
f.mViewLifecycleOwnerLiveData.setValue(null);
f.mInnerView = null;
f.mInLayout = false;
}
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
if (f.getAnimatingAway() != null || f.getAnimator() != null) {
newState = Fragment.CREATED;
} else {
boolean beingRemoved = f.mRemoving && !f.isInBackStack();
f.performDestroy();
} else{
f.mState = Fragment.INITIALIZING;
}
f.performDetach();
}
}
}
当处于Fragment.RESUMED
时,调用的是pause
,其他状态也是类似,看到DestroyView()
之后,Fragment
中所持有的View
被置为null
了。
这里还有一个点就是,Fragment
作为其他Fragment
的容器时,父Fragment
会将生命周期分发给子Fragment
。上述只是简单的介绍一些我认为的关键点,commit()
之后的完成流程是:
上图出自【背上Jetpack之Fragment】从源码角度看 Fragment 生命周期 AndroidX Fragment1.2.2源码分析
上面对Fragment
的生命周期只是进行了简单的分析,更深的理解还是需要去阅读源码才行
Fragment是如何存储与恢复的
我们知道当App处于后台被意外杀死时不仅Activity
会重建,原来添加的Fragment
也会恢复。首先来看一下Fragment
保存的时机
FragmentActivity.java
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
markFragmentsCreated();
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
}
在FragmentActivity
的onSaveInstanceState()
方法中,看到Parcelable p = mFragments.saveAllState();
在这里保存了Fragment
的状态,代码如下
@Nullable
public Parcelable saveAllState() {
return mHost.mFragmentManager.saveAllState();
}
最终调用FragmentManager.saveAllState()
,我们进入这个方法里看一下
Parcelable saveAllState() {
// 1.将未执行完成的事务和动画执行完毕
forcePostponedTransactions();
endAnimatingAwayFragments();
execPendingActions();
mStateSaved = true;
if (mActive.isEmpty()) {
return null;
}
// 2.保存mActive中的Fragment
int size = mActive.size();
ArrayList<FragmentState> active = new ArrayList<>(size);
boolean haveFragments = false;
for (Fragment f : mActive.values()) {
if (f != null) {
haveFragments = true;
FragmentState fs = new FragmentState(f);
active.add(fs);
}
}
ArrayList<String> added = null;
BackStackState[] backStack = null;
//3. 保存 mAdded中的Fragment
size = mAdded.size();
if (size > 0) {
added = new ArrayList<>(size);
for (Fragment f : mAdded) {
added.add(f.mWho);
}
}
//4. 保存 mBackStack中的数据
if (mBackStack != null) {
size = mBackStack.size();
if (size > 0) {
backStack = new BackStackState[size];
for (int i = 0; i < size; i++) {
backStack[i] = new BackStackState(mBackStack.get(i));
}
}
}
FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
}
fms.mNextFragmentIndex = mNextFragmentIndex;
return fms;
}
上面的代码经过了一些简化,不过逻辑很简单,就是将现有的数据保存到FragmentManagerState
中,步骤如下
- 保存
mActive
中的Fragment - 保存
mAdded
中的Fragment - 保存
mBackStack
中的数据 - 最后将数据保存到一个
FragmentManagerState
对象中,mAdded
和mBackStack
上面提到过,分别是添加到FragmentManager
中的Fragment
和返回栈中Fragment
,而mActive
这个列表尚未搞明白代表的意义是什么,不过也不影响我们理解保存的逻辑。
那FragmentManagerState
最终保存在哪里了呢,返回去看FragmentActivity.onSaveInstanceState()
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
}
可以看到,所有的Fragment
是保存到了一个Bundle
对象中,这个Bundle
对象通过进程间通信保存到了由AMS管理的ActivityRecord
中 的Bundle icicle;
Fragment恢复
恢复的入口在FragmentActivit.onCreate(Bundle savedInstanceState)
中
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreSaveState(p);
}
}
恢复最终还是由FragmentManager
来处理的,详细代码在void restoreSaveState(Parcelable state)
中,其中的逻辑也很简单。
不过有一点需要注意的是,Fragment 恢复是真的和重建前使用的是同一个对象吗? 有两种情况:
-
设置了RetainInstance,并且进程还在: 设置了RetainInstance,Fragment会被保存到nonConfig里,重建时会直接拿出这个已有的Fragment,不会重新创建。
-
没有设置RetainInstance,或者进程已经被kill掉了: 这种情况下,没有可以直接获得的Fragment,会通过反射创建出一个新的Fragment,这个fragment肯定和之前那个不是同一个对象。
摘自 每日一问 | Fragment 是如何被存储与恢复的? 有更新
使用Fragment需要注意的地方
-
重影问题
该问题就是当app发生重建时(横竖屏切换或者因内存不足被杀后恢复),会发现原来的
fragment
上,又盖了一层Fragment
,发生重影,代码如下@Override protected void onCreate(Bundle savedInstanceState) { getSupportFragmentManager() .beginTransaction() .add(R.id.container, new FragmentTest()) .commit(); }
正常情况下这样使用是没问题的,但是当发生重建时,根据上面对
Fragment
恢复的分析得知,系统已经帮我们做好了Fragment
的恢复,而上述代码又在onCreate()
中添加了Fragment
,所以会导致Fragment
添加两遍的问题,知道了原因我们就可以改正,如下@Override protected void onCreate(Bundle savedInstanceState) { if(savedInstanceState == null) { getSupportFragmentManager() .beginTransaction() .add(R.id.container, new FragmentTest()) .commit(); } }
-
在ViewPager中使用
先来看一段常用代码
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_pager);
viewPager = findViewById(R.id.viewPager);
List<BaseFragment> list = new ArrayList<>();
list.add(new FragmentOne());
list.add(new FragmentTwo());
list.add(new FragmentThree());
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), list));
}
private class ViewPagerAdapter extends FragmentPagerAdapter {
private List<BaseFragment> list;
public ViewPagerAdapter(FragmentManager fm, List<BaseFragment> list) {
super(fm);
this.list = list;
}
@Override
public Fragment getItem(int position) {
return list.get(position);
}
@Override
public int getCount() {
return list.size();
}
}
上述代码看起来没问题,但是当app发生重建时,list
中的Fragment
可能和ViewPagerAdapter
中的Fragment
不是同一个,这里的可能是指
- ViewPager加载过的
Fragment
: 肯定与list
中的不一样 - ViewPager没加载过的
Fragment
: 由于会调用getItem()
,所以与list
中的一样
原因如下:
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
...
return fragment;
}
通过源码可以看到,FragmentPagerAdapter
查找Fragment
时先从FragmentManager
获取,如果找不到才会调用getItem(position)
,而重建时FragmentManager
会恢复之前添加的Fragment
,对于重建的这些Fragment
由于已经存在,所以不要调用getItem(position)
,自然也就和onCreate()
中重新添加到列表list
中的Fragment
不一样。
对于FragmentStatePagerAdapter
来说,只要明白了跟FragmentPagerAdapter
的区别,就可以相同的思路去理解。
- ViewPager更新问题
当调用FragmentPagerAdapter.notifyDataSetChanged()
后,发现ViewPager
并没有改变,看源码发现最终调用如下代码:
void dataSetChanged() {
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
// 1.关键点
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
// 2.关键点
mAdapter.destroyItem(this, ii.position, ii.object);
}
}
requestLayout();
}
上述有两句关键代码,第一句newPos == PagerAdapter.POSITION_UNCHANGED
直接跳过循环,不处理,而PagerAdapter
如果不重写getItemPosition()
的话,默认就是POSITION_UNCHANGED
,所以更新必须是重getItemPosition()
如下
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
上述第二句是mAdapter.destroyItem(this, ii.position, ii.object);
对于FragmentStatePagerAdapter
来说,destroy之后FragmentManager
会remove掉Fragment
,当需要该位置的Fragment
时,调用getItem()
重新获取,这种情况下ViewPager
没有缓存的Fragment
更新是可以的,但是FragmentPagerAdapter
destroy之后只是将Fragment
detach掉,Fragment
实例并没有被移除,所以在数据变化时候需要清空掉FragmentManager管理的Fragment,才能达到更新的效果。
Fragment使用的一些技巧
- 使用一个没有界面的
Fragment
可以获取到生命周期相关的数据,例子有LifeCycle
、Glide
等 - 使用
Fragment
来动态获取权限
总结
Fragment
的生命周期由自己和Activity
的状态来共同决定Fragment
在Activity
的onSaveInstanceState
中保存起来,是保存到了AMS
中,在应用因配置改变或内存低被杀死而重建时会恢复Fragment
常见的问题,大多与重建机制有关,只要搞懂了重建机制就能处理好
参考
- 当Fragment遇上ViewPager
- 每日一问 Activity 都重建了,你 Fragment凭什么活着?
- 每日一问 | Fragment 是如何被存储与恢复的? 有更新
- 每日一问 | Activity与Fragment的那些事,“用起来没问题,我都要走了,你崩溃了?”
- 【背上Jetpack之Fragment】从源码角度看 Fragment 生命周期 AndroidX Fragment1.2.2源码分析
- 【背上Jetpack之Fragment】你真的会用Fragment吗?Fragment常见问题以及androidx下Fragment的使用新姿势
- 使用Fragment优雅地申请运行时权限
- 每日一问 ViewPager 这个流传广泛的写法,其实是有问题的!