Hi,很快乐见到你!
引言
在应用 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)