/ 今日科技快讯 /
近日,国家卫健委印发对新型冠状病毒感染实施“乙类乙管”总体方案的通知,通知提及,优化中外人员往来管理,取消入境后核酸检测及隔离政策。来华人员在行前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滚动位置)的变化,如下所示:
因为默认缓存为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是怎么还原状态的。具体的调用栈如下:
FragmentManager - moveToState()
FragmentManager - activityCreated()
Fragment - performActivityCreated()
Fragment - restoreViewState()
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)
推荐阅读:
欢迎关注我的公众号
学习技术或投稿
长按上图,识别图中二维码即可关注