0 认知
Fragment官方的翻译名为:片段,表示 Activity中的行为或用户界面部分。
相比Activity
相比Activity,Fragment的创建、销毁只需要依附到宿主Activity中,不需要与ActivityManagerService跨进程交互,所有的生命周期在宿主Activity中完成,可以在多个FragmentActivity中被多次重用,所以它更加灵活。
相比View
相比View,它拥有更多的生命周期(onAttach、onCreate、onCreateView、onStart、onResume、onPause、onStop、onDestroyView、onDestroy),可以管理menu,持有Activity引用(View持有的context有可能为ContextThemeWrapper对象),更利于模块化。
1 构造
Fragment有两种方式创建并依附到宿主Activity。
fromLayout方式
在xml中配置fragment标签,例如
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:tag="tag"
android:name="com.asha.fragmentdemo.MyDialogFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
记得fragment必须带上1、android:name;2、android:tag或android:id二选一,否则会在创建过程中检查参数时报错。
当这个layout被inflate后,LayoutInfalter会回调Activity中的FragmentManager去处理这个tag,根据android:name实例化此tag对应的Fragment对象,通知它生成并返回view。随后由FragmentManager管理此Fragment的生命周期。
FragmentManager方式
在代码中实例化Fragment,被创建一个Bundle作为参数存储载体赋值给Fragment,随后通过FragmentManager开启个transaction、add Fragment、commit,随后某个时间点,FragmentMangager会处理此commit提交的aciton,完成Fragment的依附,示例代码如下:
blankFragment = new BlankFragment();
Bundle bundle = new Bundle();
bundle.putInt("data",-1);
blankFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction().add(R.id.container, blankFragment, "BlankFragmentTag").commit();
通过setArguments(bundle)有利于保存与恢复,后面会有介绍。
2 状态
fragment的状态变化由FragmentManager管理,fragment状态主要可以分为以下6种:
//android.support.v4.app.Fragment
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.
2.1 fragment状态变化
fragment状态变化主要来自以下两个方面:
宿主FragmentActivty的生命周期变化
FragmentActivity生命周期的变化会调用FragmentController的对应回调,如当FragmentActivity调用onDestory后,成员变量FragmentController对象被调用了dispatchDestroy,代码如下
//android.support.v4.app.FragmentActivty
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
@Override
protected void onDestroy() {
super.onDestroy();
doReallyStop(false);
mFragments.dispatchDestroy();
mFragments.doLoaderDestroy();
}
对应FragmentController内传递了给了构造时聚合进来的mHost对象中的FragmentManager对象,代码如下
// android.support.v4.app.FragmentController
public void dispatchDestroy() {
mHost.mFragmentManager.dispatchDestroy();
}
随后调用到FragmentManager中的moveToState方法,处理状态改变,代码如下
// android.support.v4.app.FragmentManager
public void dispatchDestroyView() {
moveToState(Fragment.CREATED, false);
}
FragmentTransaction
与上一条直接在主线程中立即调用不同,FragmentTransaction添加一系列add、remove、replace操作op并链表形式存储,执行commit后,会在FragmentManager.enqueueAction,通过handler.post方法在主线程中下一个未知时间点执行此action,此action代码如下:
// android.support.v4.app.BackStackRecord extends FragmentTransaction
public void run() {
...
Op op = mHead;
while (op != null) {
...
switch (op.cmd) {
case OP_ADD:
...
mManager.addFragment(f, false);
break;
case OP_REPLACE:
...
mManager.removeFragment(old, transition, transitionStyle);
...
mManager.addFragment(f, false);
...
break;
case OP_REMOVE:
...
mManager.removeFragment(f, transition, transitionStyle);
break;
case OP_HIDE:
...
break;
case OP_SHOW:
...
break;
case OP_DETACH:
...
break;
case OP_ATTACH:
...
break;
default:
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
op = op.next;
}
mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);
...
}
在上述removeFragment、addFragment等操作都会进入对应的moveToState函数,最后mManager调用moveToState函数来同步目前管理的fragment状态迁移到mManager.mCurState状态。
2.2 moveToState
FragmentManager中moveToState函数有多个参数形式,moveToState方法所有形参定义如下:
// android.support.v4.app.FragmentManager
void moveToState(Fragment f)
void moveToState(int newState, boolean always)
void moveToState(int newState, int transit, int transitStyle, boolean always)
void moveToState(Fragment f, int newState, int transit, int transitionStyle,boolean keepActive)
举个例子,当FragmentActivity已经onResume后,即FragmentActivity已经显示在屏幕中,此时FragmentActivity中onResume已经调用了mFragmentManager的dispatchResume函数,即
// android.support.v4.app.FragmentManager
public void dispatchResume() {
mStateSaved = false;
moveToState(Fragment.RESUMED, false);
}
通过层层调用,进入到了上述第三个moveToState,在此代码中,FragmentManager实例的成员变量mCurState直接被赋值为Fragment.RESUMED状态,随后遍历实例内管理的mActive数组中的fragment对象,让他们进入到Fragment.RESUMED状态,代码如下:
// android.support.v4.app.FragmentManager
int mCurState = Fragment.INITIALIZING;
void moveToState(int newState, int transit, int transitStyle, boolean always) {
...
mCurState = newState;
if (mActive != null) {
...
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null) {
moveToState(f, newState, transit, transitStyle, false);
...
}
}
...
}
}
先忽略现有fragment的状态迁移,如果此时有新的fragment通过FragmentTransaction加入到mManager(为FragmentManager实例),上面分析过android.support.v4.app.BackStackRecord会在某个时间点执行action,此时新的frament被加入到mManager.mActive数组中,同时会调用mManager.moveToState同步到现有状态,即Fragment.RESUME/5,代码如下:
// android.support.v4.app.BackStackRecord extends FragmentTransaction
public void run() {
...
// mManager为FragmentManager实例
mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);
...
}
所以执行到了FragmentManager的第四个moveToState,此时新的Fragment刚被创建成功,成员变量mState默认INITIALIZING/0,
// android.support.v4.app.Fragment
int mState = INITIALIZING;
FragmentManager的第四个moveToState函数带有Fragment参数,进入后先会使用f.mState(即fragment当前状态)与目标状态newState进行比较,f.mState即0 < newState即5,则fragment需要从状态0到状态5,分别需要经历INITIALIZING/0、CREATED/1、ACTIVITY_CREATED/2、STOPPED/3、STARTED/4,最后赋值为5,注意switch中没有break,需要一直按顺执行,不同的状态分支需要执行不同的函数通知fragment进入此状态,同时回调fragment中对应的生命周期函数。从代码框架如下:
// android.support.v4.app.FragmentManager
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
if (f.mState < newState) {
...
switch (f.mState) {
case Fragment.INITIALIZING:
...
f.onAttach(mHost.getContext());
...
if (!f.mRetaining) {
f.performCreate(f.mSavedFragmentState);
}
f.mRetaining = false;
if (f.mFromLayout) {
...
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
case Fragment.CREATED:
if (newState > Fragment.CREATED) {
if (!f.mFromLayout) {
...
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
f.performActivityCreated(f.mSavedFragmentState);
...
}
case Fragment.ACTIVITY_CREATED:
case Fragment.STOPPED:
if (newState > Fragment.STOPPED) {
f.performStart();
}
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
...
f.performResume();
...
}
}
} 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.STOPPED:
if (newState < Fragment.STOPPED) {
f.performReallyStop();
}
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {
...
f.performDestroyView();
...
}
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
...
if (!f.mRetaining) {
f.performDestroy();
}
f.onDetach();
}
}
}
f.mState = newState;
}
同理从RESUMED状态被destroy,需要从5迁移到0,执行上述函数f.mState > newState部分的逻辑,一步一步回到INITIALIZING状态。
当然如果宿主Activity与fragement同时被销毁,fragement会接收到FragmentActivity对应的生命周期dispatch,从5到4,从4到3,从3到2,从2到1,从1到0即可完成状态迁移。
3 生命周期
先来一张官方文档的图,第二章已经介绍了状态迁移,对应的状态改变会调用对应的生命周期回调,调用时机已经非常清晰,说一下几个注意点:
onAttach后即持有activity引用
不要被onActivityCreated迷惑,fragment.onAttach时就可以使用activity了,因为在f.onAttach前就进行了一系列基础变量的赋值,代码如下:
// android.support.v4.app.FragmentManager
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
if (f.mState < newState) {
...
switch (f.mState) {
case Fragment.INITIALIZING:
...
f.mHost = mHost;
f.mParentFragment = mParent;
f.mFragmentManager = mParent != null
? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
f.mCalled = false;
f.onAttach(mHost.getContext());
...
}
...
}
...
}
onActivityCreated是一个什么状态?
FragmentActivity在调用onCreate开始的时候调用FragmentManager实例的dispatchCreate函数,在FragmentActivity在调用onCreate结束的时候会调用dispatchActivityCreated,即通知fragment的activity已经调用onCreate完毕。
为什么fromLayout的Fragment在INITIALIZING阶段就需要onViewCreated?
FragmentActivity在onCreate的时候调用了setContentView,此时需要通过R.layout.xxx方式或者直接LayoutInflater的方式创建view,LayoutInflater创建view时在createViewFromTag函数内会根据xml里定义的tag和attr去实例化对应的View,当LayoutInflater读取到fragment这个tag后,先让LayoutInflater内的context去处理onCreateView,看是否能返回对应的view,层层调用进入FragmentManager.onCreateView去实例化fragment、赋值、状态迁移,关键是在这个时候就需要返回fragment内对应的view,此时FragmentActivity在onCreate阶段,FragmentManager应该在Fragment.CREATED阶段,所以状态同步时newStateFragment.CREATED,在Fragment.CREATED分支无法调用onViewCreated,而fromLayout的Fragment在INITIALIZING阶段就需要创建view并返回了,所以在INITIALIZING就得调用了onViewCreated了。
如果fragment.setRetainInstance(true),在一定情况下生命周期函数调用就发生改变了
这个一定情况是指configChange的情况,下一章具体讲。
4 状态保存
4.1 需要保存哪些东西?
Fragment
如果需要重新构造一个除了内存地址不一样,属性与原来实例一模一样的Fragment,需要序列化以下四个对象或属性:
- FragmentState
FragmentState包含了重新构造这个Fragment所需的最基本的属性,包括完整类名、在mActive内的index,是否从layout生成的,id,tag,容器id,是否retainInstance,是否已经detached,构造时传入的参数,定义如下:
// android.support.v4.app.FragmentState
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
mFromLayout = frag.mFromLayout;
mFragmentId = frag.mFragmentId;
mContainerId = frag.mContainerId;
mTag = frag.mTag;
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
}
- ViewState
Fragment内部管理的view的状态,我们知道view自身有一套状态保存的机制,通过根节点的view一层一层dispatch出去(dispatchSaveInstanceState、dispatchRestoreInstanceState)触发保存和恢复先前的状态。这种恢复属于view被新建实例后恢复原来的状态,比如EditText选中了一段文字,旋转屏幕重新创建view实例,会重新focus,重新选中刚刚所选的那段文字。
而由Fragment管理的view脱离了原来的dispatch流程,是由Fragment自主管理触发saveViewState和restoreViewState,脱离dispatch的方法在sdk11之前wrap一个NoSaveStateFrameLayout,11及之后直接设置属性即可,代码如下:
// android.support.v4.app.FragmentManager
// moveToState
f.mView = f.performCreateView(f.getLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
if (Build.VERSION.SDK_INT >= 11) {
ViewCompat.setSaveFromParentEnabled(f.mView, false);
} else {
f.mView = NoSaveStateFrameLayout.wrap(f.mView);
}
...
}
- mUserVisibleHint
用这个标记可以控制Fragment是否延迟执行mLoaderManager内的任务,即如果mUserVisibleHint为false,这个Fragment在需要迁移到大于STOPPED的状态时先忽略,当所有其他mUserVisibleHint为true的Fragment内的runningLoader执行完成,再迁移到FragmentManager现有状态。
那为什么是停止在STOPPED状态?
因为这个状态下一个状态就会触发Fragment.onStart,onStart会调用这个Fragment内的mLoaderManager启动内部的loader去做加载操作,如果延迟加载这部分,可以让其他更重要的loader做完操作后再进行,提升体验。
- 可自定义的onSaveInstanceState
这个与Activity一致,不再赘述。
FragmentManager
FragmentManager与Fragment一样,都有一个序列化、反序列化基础属性的State类:FragmentManagerState。FragmentManagerState保存三个可序列化对象数组:
// android.support.v4.app.FragmentManagerState
final class FragmentManagerState implements Parcelable {
FragmentState[] mActive;
int[] mAdded;
BackStackState[] mBackStack;
}
对应保存FragmentManager内的三个数组属性,定义如下:
// android.support.v4.app.FragmentManager
ArrayList<BackStackRecord> mBackStack;
ArrayList<Fragment> mActive;
ArrayList<Fragment> mAdded;
mAdded内保存的是所有add到FragmentManager内的Fragemnt,mActive中包含了所有mAdded对象外,还保存了与backStack相关的所有Fragment。所以说mAdded是mActive的子集,对应序列化对象时,mAdded只需要记住这个Fragment对象在mActive中的索引值,就可以找回原来Fragment对应的新Fragment。mBackStack保存了所有addToBackStack的FragementTransaction,可以记录某次commit操作所有Fragment的变化,便于按下back键后回滚到上一步。
4.2 Fragment保存机制
fragment有两种保存机制,一种是fragment.onSaveInstanceState方式,另一种是fragment.setRetainInstance(true)方式,我们来看看以下几个经常出现的场景:
宿主FragmentActivity从后台直接恢复
由于FragmentActivity只是在onStop状态,FragmentActivity内的FragmentManager实例状态为STOPPED状态,FragmentManager实例和其内部管理的Fragment实例都还健在,只是需要从STOPPED状态迁移到RESUMED即可。
FragmentActivity的recreate
recreate有两种情况会触发,一种是直接调用Activity.recreate(),另一种是RELAUNCH_ACTIVITY。两种方式走到AMS层后都是走相同的流程。
RELAUNCH_ACTIVITY会在旋转屏幕等onConfigurationChanged的情况未被Activity处理后发生。例如发生了ConfigurationChanged,而Manifest.xml中此Activity的android:configChanges没有配置此Configuration,即Activity不处理此Configuration,AMS就会RELAUNCH此Activity。
发生recreate后,AMS会销毁现有的Activity实例,重新启动一个新的Activity实例。
如果Fragment设置了fragment.setRetainInstance(true)
AMS在销毁旧Activity实例时会调用ActivityThread.performDestoryActivity -> Activity.retainNonConfigurationInstances -> FragmentActivity.onRetainNonConfigurationInstances -> FragmentController.retainNonConfig -> mFragmentManager.retainNonConfig,在FragmentManager中返回了mActive数组拷贝,代码如下:
// android.support.v4.app.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;
...
}
}
}
return fragments;
}
我们来看看Activity中保存一部分实例,代码如下:
// android.app.Activity
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
List<Fragment> fragments = mFragments.retainNonConfig();
ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}
nci所保存的对象可以是任意对象,不需要做序列化和反序列化操作,恢复时是原有对象实例。nci对象返回给ActivityThread,保存在此进程的ActivityThread对象的mActivities键值对的此Activity Binder对应的ActivityClientRecord中。值得注意的是,nci.activity是在Activity中调用onRetainNonConfigurationInstance返回的对象,不是此Activiyt实例。
所以原Activity被recreate后,会生成新的Activity,新的FragmentManager,但是是旧的Fragment对象,所以它的生命周期会有所不同,它不会有onCreate和onDestroy,不会被FragmentManager从mActive中删除,因为在Activity onDestroy时需要被retainNonConfig保存下来。
当新Activity onCreate后会调用FragmentManager的restoreAllState,在此把之前保存在nci对象里的mActive重新取出来,一个一个赋值给新建的mActive对象,完成nonConfig对象的恢复。
如果Fragment未设置fragment.setRetainInstance(true),默认为false
则走原有流程。
宿主FragmentActivity从后台恢复时由于内存不足已经被kill
众所周知Activity的onSaveInstanceState在onPause之后在onStop之前,所以当Activity在被放到后台即onStop前会调用onSaveInstanceState,在此函数中调用了FragmentManager.saveAllState,主要存储FragmentManager的三个数mActive、mAdded、mBackStack的内容到FragmentManagerState中,mActive中保存了上述的FragmentState。当Activity被重新创建调用onCreate时会得到刚刚保存的savedInstanceState,再通过这个savedInstanceState获得刚保存的FragmentManagerState,去创建一个新的FragmentManager对象,去重新生成Fragment。此时恢复的FragmentActivity、FragmentManager、Fragment都是新的实例。
此时不管fragment是否setRetainInstance(true),Fragment实例都会重新被创建,原因一:retainNonConfig是在Activity在onDestroy被保存的;原因二:只有被relaunch的activity在destroy时才会在ActivityThread代码中被调用retainNonConfig去通知Activity返回需要保存实例,其他的destroy不会。
4.3 补充
3.0后处理旋转屏幕ConfigurationChanged
3.0后需要配置screenSize来处理旋转屏幕ConfigurationChanged
<activity android:name=".MainActivity" android:configChanges="orientation|screenSize">
如果配置了这个属性,旋转屏幕,Activity只会回调onConfigurationChanged,不会调用其他任何生命周期函数,当然也不会被重新生成实例,FragmentManager实例、Fragment实例都是不会发生变化的。
5 小细节
remove fragment
如果Fragment通过layout.xml方式加入到Activity中,被FragmentManager进行remove或者replace操作后,Fragment实例在FragmentManager中被删去,而Fragment内对应的view没有被赋值mContainerView,所以内部的view没有被移除,导致界面一直存在,此时这个Fragment已经被detach,如果再对它调用getActivity将返回null。
add fragment
如果Activity没有处理screenSize的onConfigurationChanged,那么此Activity将被recreate。在重新调用Activity onCreate时,FragmentManager的mActive和mAdd内的Fragment被重新创建,如果此时在Activity的onCreate重新add一个Fragment,那么就会出现两个Fragment的情况。
如何解决?在添加前先检查下FragmentManager内是否存在此Fragment,不存在再添加即可,代码如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(v);
BlankFragment blankFragment = (BlankFragment) getSupportFragmentManager().findFragmentByTag("BlankFragmentTag");
if ( blankFragment == null ){
blankFragment = new BlankFragment();
blankFragment.setUserVisibleHint(false);
Bundle bundle = new Bundle();
bundle.putInt("data",-1);
blankFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction().add(R.id.container, blankFragment, "BlankFragmentTag").commit();
}
}
hide fragment
因为hide状态没有被保存下来,在recreate或者内存不足重启Activity的情况下,原来被hide的Fragment将被重新show,所以得注意这个问题。
replace fragment
目前的版本存在一个bug,一个ViewGroup加了一个以上Fragment后,FragmentManager去replace此ViewGroup内的Fragement无法正确replace,原因是在ArrayList for循环里做了ArrayList remove操作,目前还没修复。
addBackStack情况下,remove Fragment后Fragment还继续存在mActive中
由于remove后有可能popBackStack(),所以mAdd内被删除后,mActive内还保存着此Fragment引用,此时findFragmentById或者findFragmentByTag都可以找到这个Fragment。同理,setRetainInstance(true)的Fragment被remove后也存在在mActive中。
DialogFragment中的onCreateView
同一个activity中的fragment和DialogFragment用onCreateView中所带的参数inflater去inflate view,view.getContext()返回值是不同的。。前者是此activiy;后者返回的是ContenxThemeWrapper,内部wrap了activity。原因是dialog要创建新的context存放对应的theme去inflate view。
6 reference
本文所涉及源码版本为
compile 'com.android.support:support-v4:23.1.0'