Fragment源码阅读笔记

ReyIronside 9年前

来自: 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>