opoojkk

重建时Fragment发生了什么

xx
目次

当Activity的配置发生变化或者低内存被回收后,会触发Activity的重建。可以通过重写onSaveInstanceStateonRestoreInstanceState实现重建时的保存和恢复逻辑。

Fragment的本质上是对View的封装,为其增加了生命周期的支持,因此能够在一个Activity中添加多个Fragment。Fragment重建也是依赖宿主Activity的。现在,当我们创建Activity实例时,看到的往往是继承自AppCompatActivity的,AppCompatActivity到Activity之间隔了很多代。

联系 #

和Fragment关系最密切的,是一个叫FragmentActivity的。它的里面有一个名叫mFragments的,实际上是FragmentController类型的一个实例。

FragmentController中,有两个关键类完成Fragment的逻辑,一个是FragmentManager实现Fragment的add、replace、remove。另一个是FragmentStateManager,它实现的是更新Fragment的状态,让Fragment执行对应的生命周期方法,还有一个对当前主题很重要的,执行保存状态也是由它发起,通知Fragment执行实现的。

保存 #

Activity的重建和Fragment时怎么联系起来的呢?

在Activity中的重建方法我们知道是onSaveInstanceState和onResotreInstanceState,但是如果在FragmentActivity中找,是找不到这个两个方法的,一直到父类ComponentActivity中才能看到。

1
2
3
4
5
6
7
8
@CallSuper
override fun onSaveInstanceState(outState: Bundle) {
    if (lifecycle is LifecycleRegistry) {
        (lifecycle as LifecycleRegistry).currentState = Lifecycle.State.CREATED
    }
    super.onSaveInstanceState(outState)
    savedStateRegistryController.performSave(outState)
}

和Fragment的秘密只有可能在父类或者savedStateRegistryController中,父类帮大家看过了,只有一些更新状态的逻辑,再有就到Activity类了。只可能是savedStateRegistryController完成的了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    /**
     * An interface for an owner of this [SavedStateRegistry] to perform state saving, it will call
     * all registered providers and merge with unconsumed state.
     *
     * @param outBundle SavedState in which to place a saved state
     */
    @MainThread
    internal fun performSave(outBundle: SavedState) {
        val inState = savedState {
            restoredState?.let { putAll(it) }
            synchronized(lock) {
                for ((key, provider) in keyToProviders) {
                    putSavedState(key, provider.saveState())
                }
            }
        }

        if (inState.read { !isEmpty() }) {
            outBundle.write { putSavedState(SAVED_COMPONENTS_KEY, inState) }
        }
    }

看着注释,十有八九就是我们要找的了,这个putSavedState是最后保存下来的,遍历的是keyToProviders,只要找到Fragment和keyToProviders的关系就能联系起来了。

再看FragmentActivity,初始化时有这么一段代码:

1
2
3
4
5
getSavedStateRegistry().registerSavedStateProvider(LIFECYCLE_TAG, () -> {
    markFragmentsCreated();
    mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
    return new Bundle();
});

当真的以为找到了的时候,发现事情不对。刚刚还说Fragment相关的内容都在FragmentController里,这里面也没有用到FragmentController啊。

对啊…这样找不到,那就换个方式,正着不行,反着来,从Fragment开始找。

当Fragment保存状态时,也会执行onSaveInstanceState方法,得到一些列保存的状态和对应的值。

因此能顺利的找到FragmentManager中保存的方法saveAllStateInternal。只要不断找调用的方法,就能找到了。 巧的是,在FragmentManager绑定FragmentController时,向keyToProviders中添加了一个键值对。

1
2
3
4
5
6
SavedStateRegistry registry =
                    ((SavedStateRegistryOwner) mHost).getSavedStateRegistry();
registry.registerSavedStateProvider(SAVED_STATE_KEY, () -> {
        return saveAllStateInternal();
    }
);

好家伙,这不就是刚才没对上的,Activity和Fragment总算对接上了。

问题又来了,怎么保存呢所有Fragment呢?继续看saveAllStateInternal方法吧,都在里面呢。在开始没有讲,一个Fragment对应一个FragmentStateManager,因此FragmentStateManager执行保存实际上就是用了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
# FragmentStateManager.java
    @NonNull
    Bundle saveState() {
        Bundle stateBundle = new Bundle();
        if (mFragment.mState == Fragment.INITIALIZING) {
            // We never even got to ATTACHED, but we could still have some state
            // set by setInitialSavedState so we'll add that to our initial Bundle
            if (mFragment.mSavedFragmentState != null) {
                stateBundle.putAll(mFragment.mSavedFragmentState);
            }
        }

        // Save the library state associated with the Fragment
        FragmentState fs = new FragmentState(mFragment);
        stateBundle.putParcelable(FRAGMENT_STATE_KEY, fs);

        // Save the user state associated with the Fragment
        if (mFragment.mState > Fragment.INITIALIZING) {
            Bundle savedInstanceState = new Bundle();
            mFragment.performSaveInstanceState(savedInstanceState);
            if (!savedInstanceState.isEmpty()) {
                stateBundle.putBundle(SAVED_INSTANCE_STATE_KEY, savedInstanceState);
            }
            mDispatcher.dispatchOnFragmentSaveInstanceState(mFragment, savedInstanceState, false);

            Bundle savedStateRegistryState = new Bundle();
            mFragment.mSavedStateRegistryController.performSave(savedStateRegistryState);
            if (!savedStateRegistryState.isEmpty()) {
                stateBundle.putBundle(REGISTRY_STATE_KEY, savedStateRegistryState);
            }

            Bundle childFragmentManagerState =
                    mFragment.mChildFragmentManager.saveAllStateInternal();
            if (!childFragmentManagerState.isEmpty()) {
                stateBundle.putBundle(CHILD_FRAGMENT_MANAGER_KEY, childFragmentManagerState);
            }

            if (mFragment.mView != null) {
                saveViewState();
            }
            if (mFragment.mSavedViewState != null) {
                stateBundle.putSparseParcelableArray(VIEW_STATE_KEY, mFragment.mSavedViewState);
            }
            if (mFragment.mSavedViewRegistryState != null) {
                stateBundle.putBundle(VIEW_REGISTRY_STATE_KEY, mFragment.mSavedViewRegistryState);
            }
        }

        if (mFragment.mArguments != null) {
            stateBundle.putBundle(ARGUMENTS_KEY, mFragment.mArguments);
        }
        return stateBundle;
    }

恢复 #

恢复时同样从Activity开始,先是Activity的生命周期方法:onCreate、onStart然后是onRestoreInstanceState。 恢复Fragment流程和保存大致是相似的,不过顺序是相反的,就不再重复。

倒是有个问题不知道你有没有印象,创建Activity时抛出的不能初始化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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// FragmentStateManager.java
    /**
     * Recreate a FragmentStateManager from a FragmentState instance, instantiating
     * a new Fragment from the {@link FragmentFactory}.
     *
     * @param dispatcher Dispatcher for any lifecycle callbacks triggered by this class
     * @param fragmentStore FragmentStore handling all Fragments
     * @param classLoader ClassLoader used to instantiate the Fragment
     * @param fragmentFactory FragmentFactory used to instantiate the Fragment
     * @param state Bundle used to restore the state correctly
     */
    @SuppressWarnings("deprecation")
    FragmentStateManager(@NonNull FragmentLifecycleCallbacksDispatcher dispatcher,
            @NonNull FragmentStore fragmentStore,
            @NonNull ClassLoader classLoader,
            @NonNull FragmentFactory fragmentFactory,
            @NonNull Bundle state) {
        mDispatcher = dispatcher;
        mFragmentStore = fragmentStore;

        // Instantiate the fragment's library states in FragmentState
        FragmentState fs = state.getParcelable(FRAGMENT_STATE_KEY);
        mFragment = fs.instantiate(fragmentFactory, classLoader);
        mFragment.mSavedFragmentState = state;

        // Instantiate the fragment's arguments
        Bundle arguments = state.getBundle(ARGUMENTS_KEY);
        if (arguments != null) {
            arguments.setClassLoader(classLoader);
        }
        mFragment.setArguments(arguments);

        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
            Log.v(TAG, "Instantiated fragment " + mFragment);
        }
    }

// FragmentState.java
    /**
     * Instantiates the Fragment from this state.
     */
    @NonNull
    @SuppressWarnings("deprecation")
    Fragment instantiate(@NonNull FragmentFactory fragmentFactory,
            @NonNull ClassLoader classLoader) {
        Fragment fragment = fragmentFactory.instantiate(classLoader, mClassName);
        fragment.mWho = mWho;
        fragment.mFromLayout = mFromLayout;
        fragment.mRestored = true;
        fragment.mFragmentId = mFragmentId;
        fragment.mContainerId = mContainerId;
        fragment.mTag = mTag;
        fragment.mRetainInstance = mRetainInstance;
        fragment.mRemoving = mRemoving;
        fragment.mDetached = mDetached;
        fragment.mHidden = mHidden;
        fragment.mMaxState = Lifecycle.State.values()[mMaxLifecycleState];
        fragment.mTargetWho = mTargetWho;
        fragment.mTargetRequestCode = mTargetRequestCode;
        fragment.mUserVisibleHint = mUserVisibleHint;
        return fragment;
    }

    // FragmentFactory.java
        /**
     * Create a new instance of a Fragment with the given class name. This uses
     * {@link #loadFragmentClass(ClassLoader, String)} and the empty
     * constructor of the resulting Class by default.
     *
     * @param classLoader The default classloader to use for instantiation
     * @param className The class name of the fragment to instantiate.
     * @return Returns a new fragment instance.
     * @throws Fragment.InstantiationException If there is a failure in instantiating
     * the given fragment class.  This is a runtime exception; it is not
     * normally expected to happen.
     */
    @NonNull
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
        try {
            Class<? extends Fragment> cls = loadFragmentClass(classLoader, className);
            return cls.getConstructor().newInstance();
        } catch (java.lang.InstantiationException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (NoSuchMethodException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": could not find Fragment constructor", e);
        } catch (InvocationTargetException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": calling Fragment constructor caused an exception", e);
        }
    }

所以创建Fragment(DialogFragment)最好只定义无参的构造方法,需要的参数通过Bundle传过去,这样重建的时候能创建出来,传过去的参数也能保存下来,重建后状态都是正常的。

标签:
Categories: