现象
使用同一个 DialogFragment 对象,在 dismiss 后又重新 show Fragment会自动恢复 dismiss 之前 View状态,如:EditText 会自动恢复 dismiss 之前输入的值,哪怕想在 Fragment#onCreateView 中重新为 EditText 赋值都会被覆盖成 dismiss 之前的值,造成这个问题的原因就是 Fragment 中View 状态保存和恢复机制导致的。
源码分析
-
先从 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 被添加或移除时,都会调用到
总结一下 Fragment 中 View 状态保存 Api 调用流程:
FragmentStateManager:
moveToExpectedState -> saveViewStateView:
saveHierarchyState -> dispatchSaveInstanceState -> onSaveInstanceState
- 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 版本分析, 如果有错误的地方还请多多指教。