Fragment,一个因爱生恨的组件。兼容大屏,适配多尺寸,持久化状态,作为加载器,Fragment都行。既然如此通用,那就用起来。随着项目UI越演复杂,功能需求日渐增多,突然发现出现了很多无可理喻的bug,而且都是跟Fragment密切相关的。何以解忧吗,唯有源码。
源码版本是android support 23.4.0,Fragment用的是v4包下。
1.0 第一刀 FragmentManager
Fragment的使用分为两种情况
- 依附于宿主FragmentActivity
- 嵌套Fragment
以下情况比较常见,作为例子最好不过。
宿主Activity(只针对FragmentActivity及其子类)嵌套一个父Fragment,在父Fragment下又嵌套三个同级的子Fragment。
思考如何取到各自的FragmentManager?和什么时候需要用哪层的FragmentManager?。
1.1 Activity FragmentManager
Activity获取FragmentManager的方法有
getSupportFragmentManager();
跟踪源码后发现指向FragmentHostCallback
final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
FragmentManagerImpl getFragmentManagerImpl() {
return mFragmentManager;
}
横向箭头表示持有关系。
1.2 Fragment FragmentManager
Fragment获取FragmentManager的方法
getFragmentManager();
getChildFragmentManager();
跟踪第一个方法源码Fragment.java
FragmentManagerImpl mFragmentManager;
final public FragmentManager getFragmentManager() {
return mFragmentManager;
}
public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
//省略代码
mInstance.mFragmentManager = host.mFragmentManager;
}
原来还是FragmentHostCallback.mFragmentManager,与宿主Activity相同,得出以下结论:
Activity.getSupportFragmentManager()与Fragment.getFragmentManager相同。
跟踪第二个方法源码Fragment.java
// Private fragment manager for child fragments inside of this one.
FragmentManagerImpl mChildFragmentManager;
void instantiateChildFragmentManager() {
mChildFragmentManager = new FragmentManagerImpl();
mChildFragmentManager.attachController(mHost, new FragmentContainer() {
@Override
@Nullable
public View onFindViewById(int id) {
if (mView == null) {
throw new IllegalStateException("Fragment does not have a view");
}
return mView.findViewById(id);
}
@Override
public boolean onHasView() {
return (mView != null);
}
}, this);
}
先不管FragmentManager如何初始化,可以看出ChildFragmentManager是Fragment私有的。
回到图1.简单嵌套中的实际情况。
A.getSupportFragmentManager()与B.getFragmentManager()相同,取到Activity的FragmentManager。
B.getChildFragmentManager与C/D/E.getParentFragment().getChildFramgnetManager()相同,取到FramgmentB的FragmentManager。
C/D/E.getChildFramgnetManager()是没多大意义的。
2.0 第二刀 Fragment的生命周期
如图2链式持有的类图我们可以简单了解这几个类的关系。
再看一张Fragment创建的时序图
创建简单分为两步:
- onCreate,图中1~7,Fragment.mState置为Fragment.INITIALIZING。
- onCreateView,图中8~15,在10.onCreateView()会将状态置为Fragment.CREATED。
2.1 创建过程的要点
在FragmentActivity创建时就调用
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
给FragmentController.mHost赋值,而不会置空。
反观FragmentManager是在
public void attachController(FragmentHostCallback host,
FragmentContainer container, Fragment parent) {
if (mHost != null) throw new IllegalStateException("Already attached");
mHost = host;
mContainer = container;
mParent = parent;
}
中赋值,attachController会在2.attachHost()中调用,而在dispatchDestroy()中置空。mHost 为null时容易导致
if (mHost == null && newState != Fragment.INITIALIZING) { throw new IllegalStateException("No host");}
回到重点。方法moveToState()会陆续调用Fragment的生命周期方法。一直不停初始化到Fragment.performResume()方法,此时Fragment.mState置为Fragment.RESUMED(此过程略过,感兴趣请自行阅读源码)。
注:moveToState快速理解:
f.mState < newState:是fragment创建;f.mState > newState:是fragment销毁;而且,switch并没有break,需要当心。
贴出官方生命周期图:
2.2 Fragment的销毁
随着FragmentActivity调用
/**
* Dispatch onPause() to fragments.
*/
@Override
protected void onPause() {
super.onPause();
mResumed = false;
if (mHandler.hasMessages(MSG_RESUME_PENDING)) {
mHandler.removeMessages(MSG_RESUME_PENDING);
onResumeFragments();
}
mFragments.dispatchPause();
}
经过moveToState()执行了fragment.performPause(),此时Fragment.mState置为Fragment.STARTED。
接着FragmentActivity调用
/**
* Dispatch onStop() to all fragments. Ensure all loaders are stopped.
*/
@Override
protected void onStop() {
super.onStop();
mStopped = true;
mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
mFragments.dispatchStop();
}
经过moveToState()执行了fragment.performStop(),此时Fragment.mState置为Fragment.STOPPED。
看一下时序图
此时Fragment.mState置为Fragment.ACTIVITY_CREATED。
最后FragmentActivity调用
/**
* Destroy all fragments and loaders.
*/
@Override
protected void onDestroy() {
super.onDestroy();
doReallyStop(false);
mFragments.dispatchDestroy();
mFragments.doLoaderDestroy();
}
又重新调用了doReallyStop(false)确保已经走完stop的生命周期,到moveToState后接着走framgnet.performDestroy()和fragment.onDetach()。此时Fragment.mState置为Fragment.INITIALIZING。
生命周期小结:
- 从源码来看,Fragment的生命周期完全依赖与FragmentActivity,而且并不是相当严谨,这也是为什么嵌套Fragment如此容易引发各种版本兼容问题。
- 未提到动画的加载,其实会导致很多问题。
3.0 第三刀 Fragment持久化
这句代码应该写过无数遍了
setRetainInstance(true);
官方文档是
控制Activity重建时(如屏幕旋转)是否会持久化Fragment,只能用在不寸在后台堆栈中的Framgnet中,如果设置true,生命周期会有变化:不会调用onDestory(),不会调用onCreate(Bundle)...
源码如下
public void setRetainInstance(boolean retain) {
if (retain && mParentFragment != null) {
throw new IllegalStateException(
"Can't retain fragements that are nested in other fragments");
}
mRetainInstance = retain;
}
不能放在嵌套Fragment中。
看一眼FragmentActivity,在持久化状态时调用
@Override
public final Object onRetainNonConfigurationInstance() {
//...
List<Fragment> fragments = mFragments.retainNonConfig();
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.fragments = fragments;
//...
return nci;
}
跟踪到FragmentManager
ArrayList<Fragment> retainNonConfig() {
ArrayList<Fragment> fragments = null;
if (mActive != null) {
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null && f.mRetainInstance) {
if (fragments == null) {
fragments = new ArrayList<Fragment>();
}
fragments.add(f);
f.mRetaining = true;
f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
}
}
}
return fragments;
}
从mActive的列表中选出setRetainInstance(true)的Fragment。
再回来看看FragmentActivity.onCreate()
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//...
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
//...
}
FragmentActivity创建时会重新给这些Fragment赋值。
小结:setRetainInstance(true)略过了Fragment的onCreate和onDestory生命周期,等于由FragmentActivity控制Fragment的
持久化和恢复。
4.0 学到的
- 委托模式
FragmentActivity将生命周期与Fragment相关的方法委托给FragmentController,剥离了耦合关系。 - 代理模式
FragmentController的构造方法中使用FragmentHostCallback作为抽象,而FragmentActivity.HostCallbacks作为真正实现者。 - 避免使用复杂的生命周期方法
尽量不要嵌套Fragment...不要相信...依赖...避免...Fragment的生命周期,能不用就不用吧,可以使用GitHub上的更简介的三方库,但是项目中依赖太深的也只能自己踩坑自己填。
5.0 未出鞘之四五六七
Transction,动画,LoaderManager,版本兼容见Fragment源码中的七把利刃(下)。
6.0 小结
Fragment的创建,销毁,持久化,是三把利刃,可以很好地解决问题,万一出现问题,那就要当心刀刃的方向是不是自己。