RecyclerView嵌套ViewPager引发的问题
# 事件背景
由于应用首页需要整体滑动,使用了RecyclerView中嵌套ViewPager的方式,但测试发现应用被杀死后ViewPager中的数据无法恢复,功能区显示空白。
# 事件分析
因为替换为FragmentStatePagerAdapter后恢复正常,我着重对比了设置不同adapter时的状态。 先用开发者工具确定了FragmentPagerAdapter中Fragment的View未被添加到ViewPager中,而FragmentStatePagerAdapter可以添加的区别,那么首先跟踪系统恢复的流程到
# FragmentStateManager.java
void createView() {
...
if (container != null) {
addViewToContainer();
}
发现不管设置哪个Adapter,在数据恢复期间container都为空,也就是说系统不能根据Fragment之前保存的mContainerId找到它的父容器。
那为什么FragmentStatePagerAdapter又能够正常显示呢?是否是因为setAdapter后重新进行了addView?
接下来继续跟踪setAdapter后FragmentManager的变化,根据debug时的堆栈信息,我们把断点放在FragmentManager的一个重要的方法executeOpsTogether上,
# FragmentManager.java
private void executeOpsTogether(@NonNull ArrayList<BackStackRecord> records,
@NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
...
for (FragmentTransaction.Op op : record.mOps) {
Fragment fragment = op.mFragment;
if (fragment != null) {
FragmentStateManager fragmentStateManager = createOrGetFragmentStateManager(fragment);
// 这个方法中根据生命周期Fragment.VIEW_CREATED调用了上面的createView()
fragmentStateManager.moveToExpectedState();
}
}
在这个方法中确定调用上面createView前Fragment的是否还是系统恢复时的Fragment,经过调试比对发现Fragment是一个新的对象(此处我排查时先比对的State绕了一下)。
而该新对象正是Adapter初始化时传入的对象(此处为了简单每次创建view是都初始化了fragment),FragmentStatePagerAdapter没有像FragmentPagerAdapter一样先从FragmentManager中找,而直接使用了getItem返回的对象,
# FragmentStatePagerAdapter.java
public Object instantiateItem(@NonNull ViewGroup container, int position) {
// 这里的判断逻辑未能进入
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
...
// 继续走到了这里
Fragment fragment = getItem(position);
正是由于使用的新对象触发了生命周期的变化进行了重新绑定,进而保证了ViewPager能正常添加Fragment的View。
但FragmentStatePagerAdapter中为什么没有回调saveState和restoreState来避免重新创建对象呢?
# FragmentStatePagerAdapter.java
@Override
public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
if (state != null) {
...
// 正常恢复流程下FragmentStatePagerAdapter会走这里
for (String key: keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);
}
f.setMenuVisibility(false);
mFragments.set(index, f);
} else {
Log.w(TAG, "Bad fragment at key " + key);
}
}
}
}
}
接着跟踪ViewPager的onSaveInstanceState方法,发现activity退到后台后未正常回调
# ViewPager.java
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.position = mCurItem;
if (mAdapter != null) {
// 这里会调用adapter的saveState保存状态以保证数据能够正常恢复
ss.adapterState = mAdapter.saveState();
}
return ss;
}
onSaveInstanceState又是从activity的viewtree逐级调用的,来到RecyclerView的onSaveInstanceState方法中
# RecyclerView.java
@Override
protected Parcelable onSaveInstanceState() {
SavedState state = new SavedState(super.onSaveInstanceState());
if (mPendingSavedState != null) {
state.copyFrom(mPendingSavedState);
} else if (mLayout != null) {
// 这里用了layoutManager去实现
state.mLayoutState = mLayout.onSaveInstanceState();
} else {
state.mLayoutState = null;
}
return state;
}
# LinearLayoutManager.java
@Override
public Parcelable onSaveInstanceState() {
// 这里为空
if (mPendingSavedState != null) {
return new SavedState(mPendingSavedState);
}
SavedState state = new SavedState();
if (getChildCount() > 0) {
//确保layoutState不为空
ensureLayoutState();
boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
state.mAnchorLayoutFromEnd = didLayoutFromEnd;
// 这里看到只是保存了列表能显示的第一条的adapter position和偏移量,未调用childView的onSaveInstaceState进行状态保存
if (didLayoutFromEnd) {
final View refChild = getChildClosestToEnd();
state.mAnchorOffset = mOrientationHelper.getEndAfterPadding()
- mOrientationHelper.getDecoratedEnd(refChild);
state.mAnchorPosition = getPosition(refChild);
} else {
final View refChild = getChildClosestToStart();
state.mAnchorPosition = getPosition(refChild);
state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild)
- mOrientationHelper.getStartAfterPadding();
}
} else {
state.invalidateAnchor();
}
return state;
}
那么即使最后回调了onRestoreInstanceState也不会有数据(当然这里实际上也不会回调)
而FragmentPagerAdapter初始化Fragment数据时是从mFragmentManager.findFragmentByTag去查找的,肯定是能找到的
# FragmentPagerAdapter.java
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
//这里的fragment是activity恢复时创建的,但它虽然被创建了,却未能添加到ViewPager中,
//且此时它的mState已被设置为RESUMED,无法进行重新绑定,所以无法正常显示
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
// 不会走到这里了
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
...
return fragment;
}
# 总结
虽然使用了FragmentStatePagerAdapter表面上解决了问题,却让fragment重新创建了一次并重新走了一遍它生命周期,未能做到重用。所以这类嵌套了Fragment的视图,我们应该尽量避免将它们放在RecyclerView中,或者我们也可以仿照FragmentStateAdapter来改造FragmentPagerAdapter,或者直接将ViewPager替换为ViewPager2再直接配合FragmentStateAdapter使用。
# FragmentStateAdapter.java
private void scheduleViewAttach(final Fragment fragment, @NonNull final FrameLayout container) {
// 监听生命周期变化,当fragment已经创建时直接添加到容器中
mFragmentManager.registerFragmentLifecycleCallbacks(
new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentViewCreated(@NonNull FragmentManager fm,
@NonNull Fragment f, @NonNull View v,
@Nullable Bundle savedInstanceState) {
if (f == fragment) {
fm.unregisterFragmentLifecycleCallbacks(this);
addViewToContainer(v, container);
}
}
}, false);
}