Fragment源码阅读笔记
来自: http://www.jianshu.com/p/bd4a8be309c8
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 生命周期
fragment_lifecycle.png
先来一张官方文档的图,第二章已经介绍了状态迁移,对应的状态改变会调用对应的生命周期回调,调用时机已经非常清晰,说一下几个注意点:
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'</div>