ViewPager中Fragment状态保存的小知识

6883a044fe43148073b18d52982ee240.jpeg

/   今日科技快讯   /

近日,国家卫健委印发对新型冠状病毒感染实施“乙类乙管”总体方案的通知,通知提及,优化中外人员往来管理,取消入境后核酸检测及隔离政策。来华人员在行前48小时进行核酸检测、健康申报正常且海关口岸常规检疫无异常者,可进入社会面。取消“五个一”及客座率限制等国际客运航班数量管控措拖。

新政策对旅行消费的影响迅速显现:携程数据显示,发布半小时内,携程平台热门海外目的地搜索量同比大涨10倍,出境(包含中国港澳台)机票、海外酒店搜索均达到三年来峰值。

/   作者简介   /

本篇文章转载自Petterp的博客,文章主要分享了ViewPager中Fragment如何进行状态保存,相信会对大家有所帮助!

原文地址:

https://juejin.cn/post/7088328538394198029

/   引言   /

在使用ViewPager时 , 如果我们的适配器使用的是 FragmentStatePagerAdapter,那么当我们重新滑到之前已销毁的页面时,一般情况下页面的状态依然将保持不变(比如RecyclerView的滚动位置等,EditText的输入内容等),或者说View历史状态被还原了。

本文的主旨就是解释其保存与还原内部的原理以及过程。

/   基础概念   /

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

主要原因是FragmentStatePagerAdapter 每次会重建以及销毁Fragment,而FragmentPageAdapter并不会销毁实例,只是对视图做了attach和detach 。

举个例子

如下段代码所示,我们有这样一个适配器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滚动位置)的变化,如下所示:

44346cd5d7f31b6f8baff9dd0b9a307c.gif

因为默认缓存为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)

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

从0到1,用Compose搞一个桌面版的天气应用

Kotlin Flow响应式编程,操作符函数进阶

欢迎关注我的公众号

学习技术或投稿

9adf75d37a2899fb8200d01725f4375c.png

56a7f6a29c567204672ef5b4a01444ef.jpeg

长按上图,识别图中二维码即可关注

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值