关于android:ViewPager-中-Fragment-状态保存的小知识

41次阅读

共计 4853 个字符,预计需要花费 13 分钟才能阅读完成。

Hi,很快乐见到你!

引言

在应用 ViewPager 时 , 如果咱们的适配器应用的是 FragmentStatePagerAdapter,那么当咱们从新滑到之前已销毁的页面时,个别状况下页面的状态仍然将放弃不变 (比方 RecyclerView 的 滚动地位 等,EditText 的 输出内容 等), 或者说 View 历史状态被还原了。

本文的宗旨就是解释其 保留与还原外部的原理以及过程

根底概念

ViewPager 官网的适配器有两种,即 FragmentPagerAdapter 以及 FragmentStatePagerAdapter。前者实用于大量 Item 时,后者实用于多个 item。

次要起因是 FragmentStatePagerAdapter 每次会重建以及 销毁 Fragment, 而 FragmentPageAdapter 并不会销毁实例,只是对视图做了 attachdetach

举个 🌰

如下段代码所示,咱们有这样一个适配器 [MainAdapter]:

class MainAdapter(fragmentManager: FragmentManager, private val datas: List<String>) :
    FragmentStatePagerAdapter(fragmentManager) {override fun getCount(): Int {return datas.size}

    override fun getItem(position: Int): Fragment {return T1Fragment.newInstance(datas[position])
    }
}

其余代码比拟繁难, 咱们用以下层级即可代表:

MainActivity 
 ViewPager(adapter = MainAdapter , offscreenPageLimit = 1)
         Fragment(key) - (by activityViewModel)
                 RecyclerView - (data = activityViewModel.data[key])

如上所示,咱们有一个 Activity, 其外部有一个 ViewPager,ViewPager 的适配器就是咱们下面写的 MainAdapter,默认缓存 n(1)+2

Fragment 外部是一个 RecyclerView,其数据源来自 activity 级 的 ViewModel(即咱们对数据依据 key 做了缓存, 防止每次的从新初始化)

咱们做一个滚动测试, 而后再看看 Fragment 从新创立后 View 状态(RecyclerView 滚动地位) 的变动,如下所示:

因为默认缓存为 n(1)+2 , 即当咱们滑动到 item=3 时,1 页面此时已被销毁。但当咱们从新切换到 1 时,能够发现,Fragment1 中 RecyclerView滚动地位 没有变动,所以能够证实 Fragment 的状态确实是被还原了。

那这是怎么做的呢? 带着这个问题,咱们开始比较简单的源码解析环节。

Adapter 解析

间接去看 FragmentStateAdapter

  ...
  // 保留 Fragment 的状态 list
  private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();
    ...

其外部有一个名为 mSavedState 的 List, 用于保留咱们的 Fragment 状态 , 那这个 mSavedState 又会在哪里被调用呢?既然要还原以及保留,那就免不了两个中央,[初始化][销毁] , 所以咱们持续往上来看 instantiateItem()destroyItem()

destroyItem()

此办法用于销毁咱们的指定 Fragment, 其外部把以后 Fragment 的状态依据下标保留到了 mSavedState 中。

public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {Fragment fragment = (Fragment) object;
        ...
    // 防止数组长度有余导致的越界异样
    while (mSavedState.size() <= position) {mSavedState.add(null);
    }
      // 调用 mFragmentManager 去保留 Fragment 的状态, 并将其保留在了外部的 mSavedState 中
    mSavedState.set(position, fragment.isAdded()
            ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
      // 销毁 fragment
    mFragments.set(position, null);
        ...
}

instantiateItem()

此办法次要用于初始化 指定 position 对应的 Fragment。

在初始化 Fragment 时,其会通过 下标 position 从 mSavedState 找到缓存的 Fragment 状态,而后将设置给其,便于后续的应用。

public Object instantiateItem(@NonNull ViewGroup container, int position) {
      // 如果 fragment 已存在间接返回
    if (mFragments.size() > position) {Fragment f = mFragments.get(position);
        if (f != null) {return f;}
    }
        // 初始化 Fragment, 在 adapter 中,咱们须要重写此办法,实现咱们的 Fragment 初始化
    Fragment fragment = getItem(position);
    if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
      // 数组健壮性爱护
    if (mSavedState.size() > position) {
          // 获取指定地位保留的状态
        Fragment.SavedState fss = mSavedState.get(position);
        if (fss != null) {
              // 将状态从新设置给 fragment
            fragment.setInitialSavedState(fss);
        }
    }
       ...
    return fragment;
}

小结

所以咱们能够简略了解为 FragmentStatePagerAdapter 之所以能够做到状态还原, 是因为其在销毁 Fragment 时,默认缓存了以后 Fragment 的状态信息,并且以下标的形式进行了保留,当咱们在滑动 ViewPager 时,其会加载并初始化指定 position 所对应 Fragment , 并将缓存的 Fragment 的状态信息 set 进去。

Fragment 局部

通过下面的形式,咱们能够简略的晓得 ViewPager 是如何帮咱们进行状态还原与保留,那 Fragment 到底是在什么时候去应用这个状态呢?所以带着这个问题,咱们接着去看看 Fragment 的源码。

无论是 View 还是 Fragment,其都具备 这个办法 onSaveInstanceState , 既然有保留的办法,那必定也有还原的办法。

在 Fragment 中咱们去看这个办法:onViewStateRestored()

官网解释,此办法被调用时意味着 Fragment 所有状态 都曾经还原。

所以咱们间接去看看到底是在哪里调用了此办法,也就晓得 Fragment 是怎么还原状态的。具体的调用栈如下:

  1. FragmentManager – moveToState() 👇🏻
  2. FragmentManager – activityCreated() 👇🏻
  3. Fragment – performActivityCreated() 👇🏻
  4. Fragment – restoreViewState() 👇🏻
  5. Fragment – restoreViewState(Bundle) 👇🏻

FragmentManager

// 1.
// fragment 状态变动
void moveToState(){switch (f.mState) {
       // 当 view 曾经创立好时
            case Fragment.VIEW_CREATED:  fragmentStateManager.activityCreated();}
}
 
// 2. 告诉流动已创立
void activityCreated() {
      // 执行 fragment 的 ActivityCreated 办法, 相当于 fragment 与 act 已绑定
        mFragment.performActivityCreated(mFragment.mSavedFragmentState);
      // 调度 Fragment 的生命周期
        mDispatcher.dispatchOnFragmentActivityCreated(mFragment, mFragment.mSavedFragmentState, false);
 }

Fragment

// 3. 执行与 act 绑定时的逻辑
void performActivityCreated(Bundle savedInstanceState) {mChildFragmentManager.noteStateNotSaved();
        mState = AWAITING_EXIT_EFFECTS;
        mCalled = false;
              // 触发
        onActivityCreated(savedInstanceState);
           ...
        restoreViewState();
        mChildFragmentManager.dispatchActivityCreated();}

// 4. 复原视图状态
private void restoreViewState() {if (mView != null) {restoreViewState(mSavedFragmentState);
        }
        mSavedFragmentState = null;
}

// 复原具体的视图状态
final void restoreViewState(Bundle savedInstanceState) {
    // 视图状态不为 null, 则复原之前的视图层级
    if (mSavedViewState != null) {mView.restoreHierarchyState(mSavedViewState);
        mSavedViewState = null;
    }
    if (mView != null) {mViewLifecycleOwner.performRestore(mSavedViewRegistryState);
        mSavedViewRegistryState = null;
    }
    mCalled = false;
    // 告诉 view 的状态已被还原
    onViewStateRestored(savedInstanceState);
        ..
}

总结

当咱们应用 ViewPager 时,如果应用 FragmentStatePagerAdapter 作为适配器,Fragment 的状态会被被动还原,次要起因是:

  • Fragment 销毁时,会调用 destoryItem 办法,adapter 外部会被动保留了以后的 Fragment 状态,并以以后下标作为 key 存到了一个 list 汇合中,而后在调用 getItem() 初始化 Fragment 时,其会将之前保留的状态从新 set 给咱们的 Fragment 实例。
  • 当 Fragment 生命周期执行到 activityCreated 时,从而调用 restoreViewState() 触发 View 状态的复原(此时 onCreateView 已执行),而后将咱们的 view 状态还原下来。

晓得了这个概念,咱们也就能够本人做一些小扩大,比方咱们能够在局部状况下被动将咱们的 Fragment 状态保存起来,以便在前面进行复原,也即就是应用以下两个办法即可。

// 保留
FragmentManager.saveFragmentInstanceState(fragment)
// 还原
Fragment.setInitialSavedState(SavedState)

正文完
 0