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)