转自:http://blog.csdn.net/ashqal/article/details/49560183
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标签,例如
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
记得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的依附,示例代码如下:
- 1
- 2
- 3
- 4
- 5
通过setArguments(bundle)有利于保存与恢复,后面会有介绍。
2 状态
fragment的状态变化由FragmentManager管理,fragment状态主要可以分为以下6种:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
2.1 fragment状态变化
fragment状态变化主要来自以下两个方面:
宿主FragmentActivty的生命周期变化
FragmentActivity生命周期的变化会调用FragmentController的对应回调,如当FragmentActivity调用onDestory后,成员变量FragmentController对象被调用了dispatchDestroy,代码如下
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
对应FragmentController内传递了给了构造时聚合进来的mHost对象中的FragmentManager对象,代码如下
- 1
- 2
- 3
- 4
- 5
随后调用到FragmentManager中的moveToState方法,处理状态改变,代码如下
- 1
- 2
- 3
- 4
FragmentTransaction
与上一条直接在主线程中立即调用不同,FragmentTransaction添加一系列add、remove、replace操作op并链表形式存储,执行commit后,会在FragmentManager.enqueueAction,通过handler.post方法在主线程中下一个未知时间点执行此action,此action代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
在上述removeFragment、addFragment等操作都会进入对应的moveToState函数,最后mManager调用moveToState函数来同步目前管理的fragment状态迁移到mManager.mCurState状态。
2.2 moveToState
FragmentManager中moveToState函数有多个参数形式,moveToState方法所有形参定义如下:
- 1
- 2
- 3
- 4
- 5
- 6
举个例子,当FragmentActivity已经onResume后,即FragmentActivity已经显示在屏幕中,此时FragmentActivity中onResume已经调用了mFragmentManager的dispatchResume函数,即
- 1
- 2
- 3
- 4
- 5
- 6
通过层层调用,进入到了上述第三个moveToState,在此代码中,FragmentManager实例的成员变量mCurState直接被赋值为Fragment.RESUMED状态,随后遍历实例内管理的mActive数组中的fragment对象,让他们进入到Fragment.RESUMED状态,代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
先忽略现有fragment的状态迁移,如果此时有新的fragment通过FragmentTransaction加入到mManager(为FragmentManager实例),上面分析过android.support.v4.app.BackStackRecord会在某个时间点执行action,此时新的frament被加入到mManager.mActive数组中,同时会调用mManager.moveToState同步到现有状态,即Fragment.RESUME/5,代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
所以执行到了FragmentManager的第四个moveToState,此时新的Fragment刚被创建成功,成员变量mState默认INITIALIZING/0,
- 1
- 2
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中对应的生命周期函数。从代码框架如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
同理从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前就进行了一系列基础变量的赋值,代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
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,构造时传入的参数,定义如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- ViewState
Fragment内部管理的view的状态,我们知道view自身有一套状态保存的机制,通过根节点的view一层一层dispatch出去(dispatchSaveInstanceState、dispatchRestoreInstanceState)触发保存和恢复先前的状态。这种恢复属于view被新建实例后恢复原来的状态,比如EditText选中了一段文字,旋转屏幕重新创建view实例,会重新focus,重新选中刚刚所选的那段文字。
而由Fragment管理的view脱离了原来的dispatch流程,是由Fragment自主管理触发saveViewState和restoreViewState,脱离dispatch的方法在sdk11之前wrap一个NoSaveStateFrameLayout,11及之后直接设置属性即可,代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 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保存三个可序列化对象数组:
- 1
- 2
- 3
- 4
- 5
- 6
对应保存FragmentManager内的三个数组属性,定义如下:
- 1
- 2
- 3
- 4
- 5
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数组拷贝,代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
我们来看看Activity中保存一部分实例,代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
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
- 1
如果配置了这个属性,旋转屏幕,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,不存在再添加即可,代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
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。