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<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 版本分析, 如果有错误的地方还请多多指教。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容