美文网首页
Fragment 中 View 状态保存与恢复源码分析

Fragment 中 View 状态保存与恢复源码分析

作者: 小木桨 | 来源:发表于2022-04-28 01:28 被阅读0次

    现象

    使用同一个 DialogFragment 对象,在 dismiss 后又重新 show Fragment会自动恢复 dismiss 之前 View状态,如:EditText 会自动恢复 dismiss 之前输入的值,哪怕想在 Fragment#onCreateView 中重新为 EditText 赋值都会被覆盖成 dismiss 之前的值,造成这个问题的原因就是 Fragment 中View 状态保存和恢复机制导致的。

    源码分析

    1. 先从 Fragment 中 View状态保存代码分析

      • 在 Fragment 被添加或移除时,都会调用到 FragmentStateManager#moveToExpectedState 方法,这个方法会根据当前 Fragment#mState 状态值以及期望状态值来做相应的操作,在被移除时会走到Fragment.AWAITING_EXIT_EFFECTS 这个 case,发现里面会调用 saveViewState 方法保存 View 状态
      void moveToExpectedState() {
      ...
        case Fragment.AWAITING_EXIT_EFFECTS:
      ...
             if (mFragment.mSavedViewState == null) {
                   saveViewState();
             }
      ...
         break;
      ...
      }
      
      • saveViewState 方法很简单,就是创建了一个 SparseArray 对象,然后调用 View#saveHierarchyState 方法,最终将结果保存到 Fragment#mSavedViewState
      void saveViewState() {
           if (mFragment.mView == null) {
               return;
           }
           SparseArray<Parcelable> mStateArray = new SparseArray<>();
           mFragment.mView.saveHierarchyState(mStateArray);
           if (mStateArray.size() > 0) {
               mFragment.mSavedViewState = mStateArray;
           }
           ...
       }
      
      • View#saveHierarchyState 方法内部又调用了 View#dispatchSaveInstanceState 方法,在这个方法又去调用了 onSaveInstanceState方法,这个方法才是真正去保存 View 状态的实现,每个 View 根据自己本身特点来重写这个方法最终返回一个 Parcelable 对象,然后将返回的 Parcelable 对象保存到 SparseArray 中,为了恢复状态时可以找到对应的数据,所以保存的 key 就是当前 View 的ID, 如果当前对象是 ViewGroup 则会循环遍历并调用 Children 的 dispatchSaveInstanceState 方法, 这样就完成 View 的状态保存。
      protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
           if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
               mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
               Parcelable state = onSaveInstanceState();
               if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
                   throw new IllegalStateException(
                           "Derived class did not call super.onSaveInstanceState()");
               }
               if (state != null) {
                   // Log.i("View", "Freezing #" + Integer.toHexString(mID)
                   // + ": " + state);
                   container.put(mID, state);
               }
           }
       }
      

    总结一下 Fragment 中 View 状态保存 Api 调用流程:

    FragmentStateManager:
    moveToExpectedState -> saveViewState

    View:
    saveHierarchyState -> dispatchSaveInstanceState -> onSaveInstanceState

    1. Fragment 中 View 状态恢复源码分析:
    • 上面说了在 Fragment 被添加或移除时,都会调用到 FragmentStateManager#moveToExpectedState 方法,只不过是通过 Fragment#mState 状态值来走不同的 case 来区分的,Fragment 在被恢复时走的是 Fragment.AWAITING_EXIT_EFFECTS 调用的是 activityCreated 方法
      void moveToExpectedState() {
      ...
        case Fragment.AWAITING_EXIT_EFFECTS:
                                activityCreated();
                                break;
      ...
      }
    
    • activityCreated 方法也很简单,直接调用当前 Fragment 对象的 performActivityCreated 方法,这里的 mFragment.mSavedFragmentState 值就是我们 Fragment#onSaveInstanceState 方法中的值,最后会被传到 Fragment#onActivityCreated 方法。
    void activityCreated() {
            if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                Log.d(TAG, "moveto ACTIVITY_CREATED: " + mFragment);
            }
            mFragment.performActivityCreated(mFragment.mSavedFragmentState);
            mDispatcher.dispatchOnFragmentActivityCreated(
                    mFragment, mFragment.mSavedFragmentState, false);
        }
    
    • Fragment#performActivityCreated 方法中,会先调用 onActivityCreated 方法, 然后再调用 restoreViewState 方法,这也就解释了为什么刚开始遇到的设置了值之后又被覆盖的问题。
    void performActivityCreated(Bundle savedInstanceState) {
            mChildFragmentManager.noteStateNotSaved();
            mState = AWAITING_EXIT_EFFECTS;
            mCalled = false;
            onActivityCreated(savedInstanceState);
            if (!mCalled) {
                throw new SuperNotCalledException("Fragment " + this
                        + " did not call through to super.onActivityCreated()");
            }
            restoreViewState();
            mChildFragmentManager.dispatchActivityCreated();
        }
    
    • restoreViewState 中是先调用 View#restoreHierarchyState 方法,然后再调用 onViewStateRestored,也就是先执行 View 状态恢复方法,然后再执行 Fragment 中的状态恢复方法,所以我们可以在这个方法中执行设置操作就不会被覆盖了。
    private void restoreViewState() {
            if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                Log.d(FragmentManager.TAG, "moveto RESTORE_VIEW_STATE: " + this);
            }
            if (mView != null) {
                restoreViewState(mSavedFragmentState);
            }
            mSavedFragmentState = null;
        }
    final void restoreViewState(Bundle savedInstanceState) {
            if (mSavedViewState != null) {
                mView.restoreHierarchyState(mSavedViewState);
                mSavedViewState = null;
            }
            if (mView != null) {
                mViewLifecycleOwner.performRestore(mSavedViewRegistryState);
                mSavedViewRegistryState = null;
            }
            mCalled = false;
            onViewStateRestored(savedInstanceState);
            if (!mCalled) {
                throw new SuperNotCalledException("Fragment " + this
                        + " did not call through to super.onViewStateRestored()");
            }
            if (mView != null) {
                mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
            }
        }
    
    • View#restoreHierarchyState 方法内部直接调用了 View#dispatchRestoreInstanceState 方法, 参数中的 SparseArray 就是我们在 View 状态保存方法 View#saveHierarchyState 中的值,Key 就是每个View 的ID,Value 是每个 View 的 onSaveInstanceState 方法返回的值,取到每个 View 存储的值后,调用 onRestoreInstanceState 方法,每个 View 需要重写此方法来恢复之前保存的状态,如果是 ViewGroup 则会遍历循环 Children 的 dispatchRestoreInstanceState方法, 这样就完成了 View 状态恢复流程。
    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
            if (mID != NO_ID) {
                Parcelable state = container.get(mID);
                if (state != null) {
                    // Log.i("View", "Restoreing #" + Integer.toHexString(mID)
                    // + ": " + state);
                    mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
                    onRestoreInstanceState(state);
                    if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
                        throw new IllegalStateException(
                                "Derived class did not call super.onRestoreInstanceState()");
                    }
                }
            }
        }
    

    总结一下 Fragment 中 View 状态恢复 Api 调用流程:

    FragmentStateManager:

    moveToExpectedState -> activityCreated

    Fragment:

    performActivityCreated -> restoreViewState -> restoreViewState

    View:

    restoreHierarchyState -> dispatchRestoreInstanceState -> onRestoreInstanceState

    ps: 以上代码是基于 androidx 1.3.4 版本分析, 如果有错误的地方还请多多指教。

    相关文章

      网友评论

          本文标题:Fragment 中 View 状态保存与恢复源码分析

          本文链接:https://www.haomeiwen.com/subject/ugsryrtx.html