Wons' Blog

个人博客

Android程序猿


回首向来萧瑟处,也无风雨也无晴

Android 中Fragment的自动重建

Fragment常见的两种重建方式,一种是通过调用setRetainInstance来通知系统在重建Activity(例如屏幕配置改变)时保留此Fragment;另一种方式是系统在重建Activity时自动重建Fragment,典型例子是FragmentActivity对管理的Fragment的重建。

setRetainInstance

直接参考官方文档的解释:

Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:

  • onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
  • onCreate(android.os.Bundle) will not be called since the fragment is not being re-created.
  • onAttach(android.app.Activity) and onActivityCreated(android.os.Bundle) will still be called.

调用了此方法后,Activity被销毁时,此Fragment会被保留(进程不消亡的前提下),Activity重建时,Fragment直接挂载。

原理:当配置发生变化时,Activity进入销毁过程,FragmentManager先销毁队列中Fragment的视图,然后检查每个Fragment的retainInstance属性。如果retainInstance为false,FragmentManager会销毁该Fragment实例;如果retainInstance为true,则不会销毁该Fragment实例,Activity重建后,新的FragmentManager会找到保留的Fragment并为其创建视图。

FragmentActivity对Fragment的重建

FragmentActivity在配置改变被销毁时,FragmentManager中的Fragment状态会被保存下来,等之后Activity重建时,被销毁的Fragment也会被重建。所以在这种情况下,Activity重建时,不需要再在onCreate中创建新的Fragment,而是使用被自动重建的Fragment(可以通过tag找到)。Fragment的状态保存及重建过程可以在FragmentActivity的代码中查看。

当配置改变,Activity即将销毁时,FragmentActivity会先保存所有队列中的Fragment状态。

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        markFragmentsCreated();
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }

mFragments为FragmentManager的引用,FragmentManager.saveAllState返回一个包含需要保存的Fragment的Parcelable,此数据之后会用于重建这些Fragment。

查看FragmentManager.saveAllState方法的实现,可以看出返回的Parcelable实际为一个FragmentManagerState实例,该类的数据成员定义为:

final class FragmentManagerState implements Parcelable {
    FragmentState[] mActive;
    int[] mAdded;
    BackStackState[] mBackStack;
    int mPrimaryNavActiveIndex = -1;
    int mNextFragmentIndex;
}

FragmentState的数据成员:

final class FragmentState implements Parcelable {
    final String mClassName;
    final int mIndex;
    final boolean mFromLayout;
    final int mFragmentId;
    final int mContainerId;
    final String mTag;
    final boolean mRetainInstance;
    final boolean mDetached;
    final Bundle mArguments;
    final boolean mHidden;

    Bundle mSavedFragmentState;

    Fragment mInstance;
}

FragmentState包含描述Fragment的各个数据变量,足够从零重新创建一个被销毁的Fragment。

在保存了Fragment数据之后,Activity实例以及没有调用setRetainInstance(true)的Fragment实例都被销毁。

而当Activity重建时,被销毁的Fragment也会被重建:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);

FragmentActivity通过调用FragmentManager的restoreAllState方法,重建之前保存下来并被销毁的Fragment。

更早的文章

Android NestedScrolling解决滑动冲突问题(3) - 项目实战

实际需求在前面的两片文章中我们了解了 NestedScroll 的相关接口及一般处理逻辑。在本篇文章中就实现一个具体的联合滑动需求。Android中经常在布局中嵌入 WebView 来展示网页内容,而且WebView内部还有交互逻辑(滚动之类的),如果外部布局也要处理滚动逻辑,就会有滑动冲突,这种场景在实际项目开发中很常见,例如在含有 AppBarLayout 的 CoordinatorLayout 中嵌入一个 WebView , WebView 底部再放一个 footer 放置收藏按钮等...…

Android继续阅读