Android Fragment 状态的保存

前言

遇到了一件很有趣的事情——在屏幕旋转之后,Fragment会被重复创建,于是我十分好奇Android是如何自动保存Fragment的状态并恢复的。网上有很多介绍onSaveInstanceState()使用方法的文章,但是对这当中的细节探讨少之又少,故作此文以记录之。

事件

众所周知,Activity和View都拥有onSaveInstanceState()方法,Activity会递归调用各View的此方法并汇总数据保存到Bundle中再递交给相关system进程以方便在Activity重创建时恢复数据。

那么,Fragment呢?Fragment也是拥有子View的,必然,那些东西的数据也会被保存Bundle中,我们可以打印屏幕旋转后重建Fragment时的Bundle数据:

Bundle[{androidx.lifecycle.BundlableSavedStateRegistry.key=Bundle[{}], android:view_registry_state=Bundle[{androidx.lifecycle.BundlableSavedStateRegistry.key=Bundle[{}]}], android:view_state={2131231018=android.view.AbsSavedState$1@93a3522, 2131231019=android.view.AbsSavedState$1@93a3522, 2131231020=android.view.AbsSavedState$1@93a3522, 2131231021=android.view.AbsSavedState$1@93a3522, 2131231022=android.view.AbsSavedState$1@93a3522, 2131231023=android.view.AbsSavedState$1@93a3522}}]

可以看到,重建Fragment的Bundle中包含了其子View的数据,但是,我们打开Fragement的onSaveInstanceState()方法:

    public void onSaveInstanceState(Bundle outState) {
    }

大受震撼,它居然是空的!
那么问题来了,Fragment并没有主动保存其子View的数据,那么这些数据是在哪里被保存的?

保存调用链

我们可以从Activity的onSaveInstanceState()开始调查

    protected void onSaveInstanceState(@NonNull Bundle outState) {
        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());

        outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
        if (mAutoFillResetNeeded) {
            outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
            getAutofillManager().onSaveInstanceState(outState);
        }
        dispatchActivitySaveInstanceState(outState);
    }

Fragment寄生于Activity,可以看到,在其中,Activity会主动调用其FragmentControllersaveAllState()方法,尝试保存数据。

而这个方法似乎只是个proxy。

    public Parcelable saveAllState() {
        return mHost.mFragmentManager.saveAllState();
    }

最终会去调用FragmentManagersaveAllState()方法。

这个方法太长就不贴出来了,在其中会遍历所有active fragment,对其调用saveFragmentBasicState()方法。

    Bundle saveFragmentBasicState(Fragment f) {
        Bundle result = null;

        if (mStateBundle == null) {
            mStateBundle = new Bundle();
        }
        f.performSaveInstanceState(mStateBundle);
        dispatchOnFragmentSaveInstanceState(f, mStateBundle, false);
        if (!mStateBundle.isEmpty()) {
            result = mStateBundle;
            mStateBundle = null;
        }

        if (f.mView != null) {
            saveFragmentViewState(f);
        }
        if (f.mSavedViewState != null) {
            if (result == null) {
                result = new Bundle();
            }
            result.putSparseParcelableArray(
                    FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
        }
        if (!f.mUserVisibleHint) {
            if (result == null) {
                result = new Bundle();
            }
            // Only add this if it's not the default value
            result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint);
        }

        return result;
    }

这里分为两个部分,一个是会调用该Fragment的performSaveInstanceState()方法来递归到各个子Fragment。(具体看下面)
另一个则是调用saveFragmentViewState来保存其View的状态。

    void performSaveInstanceState(Bundle outState) {
        onSaveInstanceState(outState);
        if (mChildFragmentManager != null) {
            Parcelable p = mChildFragmentManager.saveAllState();
            if (p != null) {
                outState.putParcelable(Activity.FRAGMENTS_TAG, p);
            }
        }
    }

可以看到,在其中会去调用这个Fragment的子FragmentManager,并去调用其saveAllState(),这就回到上面去了,也就是说它会一直递归直到解开所有Fragement套娃为止。

接着来看保存子View的方法:

    void saveFragmentViewState(Fragment f) {
        if (f.mView == null) {
            return;
        }
        if (mStateArray == null) {
            mStateArray = new SparseArray<Parcelable>();
        } else {
            mStateArray.clear();
        }
        f.mView.saveHierarchyState(mStateArray);
        if (mStateArray.size() > 0) {
            f.mSavedViewState = mStateArray;
            mStateArray = null;
        }
    }

saveHierarchyState,这个方法最终会调用到该View的onSaveInstanceState()

总结

Fragment本身并不负责保存其子View数据,这个过程由Activity调用FragmentManager中的相关内容完成(虽然最终它们会被放到同一个Bundle中)

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Android Fragment 状态的保存