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

现象

使用同一个 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 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 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 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 状态保存与恢复源码分析)