Fragment那点事③保存与恢复

文章里所有分析都是根据Android Sdk 25.3.1 v4包

上一篇分析了最典型的情况下添加 Fragment 的生命周期,现在再来分析一下特殊情况下 Fragment 的生命周期有什么不同。

onSaveInstanceState

当 Activity 被系统内存杀死后会调用 onSaveInstanceState 自动保存一些状态,其中就包括托管在该 Activity 内的 Fragment 的状态。

@Override
protected void onSaveInstanceState(Bundle outState) {
  super.onSaveInstanceState(outState);
  Parcelable p = mFragments.saveAllState();
  if (p != null) {
    outState.putParcelable(FRAGMENTS_TAG, p);
  }
  if (mPendingFragmentActivityResults.size() > 0) {
    outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
    int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
    String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
    for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
      requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
      fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
    }
    outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
    outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
  }
}

在 FragmentActivity 的 onSaveInstanceState 方法中主要做了如下几件事情:

  1. 调用 mFragments.saveAllState() 通过 FragmentController 执行 FragmentManager.saveAllState() 方法,初始化一个 FragmentManagerState 实例,并且得到 mActive 的 FragmentState 数组、 mAdded 的 Fragment 的 下标(mIndex)数组和返回栈的 BackStackState 数组。从这可以看出 saveInstanceState 保存的不是 Fragment 的实例,而是 Fragment 和 和返回栈的状态。
  2. 当有没有传递回来的 startActivityForResult 事件的时候就把相应 requestCode 和源 fragment.mWho 保存。

FragmentManager.saveAllState()

在 saveAllState() 方法中是保存并返回 FragmentManager 状态的逻辑,主要分为 3 部分:

// First collect all active fragments.
int N = mActive.size();
FragmentState[] active = new FragmentState[N];
boolean haveFragments = false;
for (int i=0; i<N; i++) {
  Fragment f = mActive.get(i);
  if (f != null) {
    if (f.mIndex < 0) {
      throwException(new IllegalStateException(
        "Failure saving state: active " + f
        + " has cleared index: " + f.mIndex));
    }
    haveFragments = true;

    FragmentState fs = new FragmentState(f);
    active[i] = fs;

    if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
      fs.mSavedFragmentState = saveFragmentBasicState(f);

      if (f.mTarget != null) {
        if (f.mTarget.mIndex < 0) {
          throwException(new IllegalStateException(
            "Failure saving state: " + f
            + " has target not in fragment manager: " + f.mTarget));
        }
        if (fs.mSavedFragmentState == null) {
          fs.mSavedFragmentState = new Bundle();
        }
        putFragment(fs.mSavedFragmentState,
                    FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget);
        if (f.mTargetRequestCode != 0) {
          fs.mSavedFragmentState.putInt(
            FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,
            f.mTargetRequestCode);
        }
      }
    } else {
      fs.mSavedFragmentState = f.mSavedFragmentState;
    }
    
    if (DEBUG) Log.v(TAG, "Saved state of " + f + ": "
                     + fs.mSavedFragmentState);
  }
}
  1. 得到 mActive 的 FragmentState 数组

    遍历 mActive 中的 Fragment(还记得前面分析出栈的时候,是吧 mActive 中相应下标处设置成 null,所以得到的 FragmentState 数组相应下标处也为 null)

    • 如果 f.mIndex<0 (Fragment 在 mActive 中的下标)说明该 Fragment 不在 FragmentManager 的管理之内,无法被保存状态。
    • 然后以 Fragment 为参实例化一个 FragmentState 并把一些基础属性赋值。
    • 当 Fragment 已经初始化并且状态对象为空就调用 saveFragmentBasicState() 方法,在该方法中首先触发 onSaveInstanceState 可以让开发者保存一些自定义的属性到 Bundle 中,然后调用 saveHierarchyState 方法保存 Fragment 内 View 的状态(只有部分 View 比如 TextView 实现了 View 的 onSaveInstanceState 方法)到 Bundle 中。保存 Fragment 的 mUserVisibleHint 属性到 Bundle 中,把该 Bundle 赋值给 FragmentState.mSavedFragmentState。如果有 Target fragment 就把 mIndexmTargetRequestCode 保存到 mSavedFragmentState 属性中。
  2. 得到 mAdded 中 Fragment 的 mIndex 列表

    Fragment.mIndex 是在 mActive 中的下标,在前面分析入栈的时候,在 makeActive() 方法时设置的。

  3. 得到 mBackStackState 返回栈状态信息列表

restoreAllState()

在 FragmentActivity.onCreate() 方法中,如果savedInstanceState!=null 就调用 mFragments.restoreAllState 方法,恢复保存的 Fragment 。

NonConfigurationInstances nc =
  (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
  mFragments.restoreLoaderNonConfig(nc.loaders);
}
if (savedInstanceState != null) {
  Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
  mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
}

在上面的代码片段中还有一句 getLastNonConfigurationInstance() 这个是 FragmentActivity 用来保存 mRetainInstance = true 的 Fragment 实例对象和 Loader 实例的,这是和 saveInstanceState 不同的另一种保存机制,等下面具体分析。现在就先默认 nc=null,只分析 saveInstanceState 。

FragmentManager.restoreAllState()

最终恢复 Fragment 的具体操作是在 restoreAllState() 方法中完成的,下面分段分析一下主要逻辑分别作了什么:

  1. 如果 saved state 为空或者 mActive 为空就终止往下执行。

    // If there is no saved state at all, then there can not be
    // any nonConfig fragments either, so that is that.
    if (state == null) return;
    FragmentManagerState fms = (FragmentManagerState)state;
    if (fms.mActive == null) return;
    
    List<FragmentManagerNonConfig> childNonConfigs = null;
    
  2. 调用 FragmentState.instantiate() 方法恢复 mActive 列表,可以点进去该方法查看到底恢复了 Fragment 的哪些属性(恢复了 mHidden,mIndex 等等)。mActive 包括所有被 FragmentManager 管理的 Fragment。并且恢复 mAvailIndices 列表,这里面存放着在 mActive 中值为 null 的下标,即可以被插入 Fragment 的位置。

    // Build the full list of active fragments, instantiating them from
    // their saved state.
    mActive = new ArrayList<>(fms.mActive.length);
    if (mAvailIndices != null) {
      mAvailIndices.clear();
     }
    for (int i=0; i<fms.mActive.length; i++) {
      FragmentState fs = fms.mActive[i];
      if (fs != null) {
        FragmentManagerNonConfig childNonConfig = null;
        if (childNonConfigs != null && i < childNonConfigs.size()) {
          childNonConfig = childNonConfigs.get(i);
        }
        Fragment f = fs.instantiate(mHost, mParent, childNonConfig);
        if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
        mActive.add(f);
        // Now that the fragment is instantiated (or came from being
        // retained above), clear mInstance in case we end up re-restoring
        // from this FragmentState again.
        fs.mInstance = null;
      } else {
        mActive.add(null);
        if (mAvailIndices == null) {
          mAvailIndices = new ArrayList<Integer>();
        }
        if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
        mAvailIndices.add(i);
      }
    }
    
  3. 恢复 mAdded 列表,根据 fms.mAdded 中保存的下标位置从 mActive 中得到已经被 add 的 Fragments。

    // Build the list of currently added fragments.
    if (fms.mAdded != null) {
      mAdded = new ArrayList<Fragment>(fms.mAdded.length);
      for (int i=0; i<fms.mAdded.length; i++) {
        Fragment f = mActive.get(fms.mAdded[i]);
        if (f == null) {
          throwException(new IllegalStateException(
            "No instantiated fragment for index #" + fms.mAdded[i]));
        }
        f.mAdded = true;
        if (DEBUG) Log.v(TAG, "restoreAllState: added #" + i + ": " + f);
        if (mAdded.contains(f)) {
          throw new IllegalStateException("Already added!");
        }
        mAdded.add(f);
      }
    } else {
      mAdded = null;
    }
    
  4. 恢复返回栈 mBackStack 调用 setBackStackIndex() 方法恢复 mBackStackIndicesmAvailBackStackIndices 这两个列表是用来分配 BackStackRecord.mIndex 的,相应流程在前面入栈的时候已经分析过。

    // Build the back stack.
    if (fms.mBackStack != null) {
      mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length);
      for (int i=0; i<fms.mBackStack.length; i++) {
        BackStackRecord bse = fms.mBackStack[i].instantiate(this);
        if (DEBUG) {
          Log.v(TAG, "restoreAllState: back stack #" + i
                + " (index " + bse.mIndex + "): " + bse);
          LogWriter logw = new LogWriter(TAG);
          PrintWriter pw = new PrintWriter(logw);
          bse.dump("  ", pw, false);
          pw.close();
        }
        mBackStack.add(bse);
        if (bse.mIndex >= 0) {
          setBackStackIndex(bse.mIndex, bse);
        }
      }
    } else {
      mBackStack = null;
    }
    

在 FragmentActivity.onCreate() 中恢复玩 FragmentManager 里的所有 Fragment ,接着调用 mFragments.dispatchCreate() 开始执行这些 Fragment 的生命周期。到此第一中保存恢复 Fragment 的方式分析完了,这种方式保存的是 FragmentManager 中所有 Fragment 的状态,而不是保存的实例,然后在 FragmentActivity.onCreate() 方法中再去根据这些状态去恢复 Fragment,在子 Activity 中相应的作出一些判断当内存重启的时候就可以避免重复的去添加 Fragment。

RetainInstance

与之前保存 SaveState 不同,这种保存方式是当 Activity 被重启的时候通过 onRetainNonConfigurationInstance() 方法保存 Object。

onRetainNonConfigurationInstance()

这是一个 final 类型的方法,开发者无法 Override,FragmentActivity 里有默认的实现,返回的是 NonConfigurationInstances 对象。该对象主要包含 3 部分:

  1. 调用 onRetainCustomNonConfigurationInstance() 所以开发者要想保存一个 Object 可以重写该方法。
  2. 获取 FragmentManagerNonConfig 实例,主要逻辑是在 FragmentManager.retainNonConfig() 方法中实现的。遍历 mActive 中所有的 Fragment。
    • 添加 mRetainInstance = true 的 Fragment 到 FragmentManagerNonConfig.mFragments 列表,并设置 Fragment.mRetaining = true 来标识该 Fragment 已经被保留了实例。
    • 调用 Fragment.mChildFragmentManager.retainNonConfig() 获取ChildFragmentManager 的 FragmentManagerNonConfig,并添加到FragmentManagerNonConfig.mFragments.mChildNonConfigs 列表中(有的 Fragment 里并没有直接嵌套 Fragment,所以该 ChildFragmentManager 的 FragmentManagerNonConfig 为 null)。
  3. 保存 Loader 实例。
@Override
public final Object onRetainNonConfigurationInstance() {
  if (mStopped) {
    doReallyStop(true);
  }
  
  Object custom = onRetainCustomNonConfigurationInstance();

  FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
  SimpleArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

  if (fragments == null && loaders == null && custom == null) {
    return null;
  }
  
  NonConfigurationInstances nci = new NonConfigurationInstances();
  nci.custom = custom;
  nci.fragments = fragments;
  nci.loaders = loaders;
  return nci;
}

getLastNonConfigurationInstance()

在 FragmentActivity.onCreate() 中调用该方法得到 Activity 重建前被保存的 NonConfigurationInstances 实例,然后调用把 savedInstanceState 和 FragmentManagerNonConfig 传入 FragmentManager.restoreAllState 方法中,之前只分析了 SaveInstance 这一种情况,现在把这两种情况一起分析一下。

// First re-attach any non-config instances we are retaining back
// to their saved state, so we don't try to instantiate them again.
if (nonConfig != null) {
  List<Fragment> nonConfigFragments = nonConfig.getFragments();
  childNonConfigs = nonConfig.getChildNonConfigs();
  final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0;
  for (int i = 0; i < count; i++) {
    Fragment f = nonConfigFragments.get(i);
    if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f);
    FragmentState fs = fms.mActive[f.mIndex];
    fs.mInstance = f;
    f.mSavedViewState = null;
    f.mBackStackNesting = 0;
    f.mInLayout = false;
    f.mAdded = false;
    f.mTarget = null;
    if (fs.mSavedFragmentState != null) {
      fs.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader());
      f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray(
      FragmentManagerImpl.VIEW_STATE_TAG);
      f.mSavedFragmentState = fs.mSavedFragmentState;
    }
  }
}

在上面代码片段中可以得知系统把保存的 Fragment 实例设置给了它的 FragmentState.mInstance 并把 mAddedmInLayout 等属性设置成 false,而 Fragment 内部 View 的状态用的是 FragmentState 来更新的(分析 FragmentManager.saveAllState() 的时候讲过)。

FragmentManager.saveAllState() 方法中恢复 mActive 列表时调用下面的方法,点进去发现Fragment.mInstance != null 的时候直接就返回了 mInstance 这不正是之前保存的 Fragment 对象嘛,而不是由 SavedState 来初始化的。

Fragment f = fs.instantiate(mHost, mParent, childNonConfig);

那再来看一下被保留下来的 Fragment 即 Fragment.mRetaining = true 实例的生命周期

if (!f.mRetaining) {
  f.performCreate(f.mSavedFragmentState);
  dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
} else {
  f.restoreChildFragmentState(f.mSavedFragmentState);
  f.mState = Fragment.CREATED;
}
f.mRetaining = false;
if (!f.mRetaining) {
  f.performDestroy();
  dispatchOnFragmentDestroyed(f, false);
} else {
  f.mState = Fragment.INITIALIZING;
}

发现,当被保存下来的 Fragment 被 add 进 Activity 中的时候,并不走 onCreate 周期函数,而是直接调用 restoreChildFragmentState 去恢复 ChildFragmentManager。所以可以在 onCreate() 方法中初始化对象避免重复初始化。当 Activity 因重启被销毁的时候 Fragment 也不会走 onDestroy() 方法。

Fragment 保存于恢复分析完毕

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

推荐阅读更多精彩内容