1.背景
在内存不足的手机上,某些非前台页面会因为内存不足而销毁,此时再次进入会执行reconstruct
的逻辑,也就是 save 、restore 逻辑,此时界面展示异常
用户正在浏览大众点评的团购详情页,然后微信来了一条消息,此时打开微信,可能点评的团购详情页就被销毁了
目的:不想要当前 activity 保留状态,销毁后和重新进入页面保持一致
2.问题探索
//伪代码如下
for (int i = 0, len = Math.min(xx.size(), TAB_MAX_COUNT); i < len; i++) {
Fragment fragment = new Fragment()
fragment.setListener(listener);
mFragments.add(fragment);
}
走一遍主流程后,发现 listener 在回调的时候为空
此时幸好使用了kotlin,避免了一次明显的空指针异常,线上没有 crash
@Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
//回调adapter的getItem方法
Fragment fragment = getItem(position);
//下面代码省略
}
在几个关键节点(随缘)打断点后发现界面上展示的 fragment 不是生成的fragment,然后 debug adapter 的 getItem 方法,发现根本就没有调用 getItem 方法,因为 mFragments 中有fragment
其实吧,上述代码里面的注释已经写得很清楚了
此时便基本确定不是代码的问题,而是 FragmentStatePagerAdapter 源码实现的问题了
3. 源码查看
//Viewpager.java
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.position = mCurItem;
if (mAdapter != null) {
ss.adapterState = mAdapter.saveState();
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (mAdapter != null) {
mAdapter.restoreState(ss.adapterState, ss.loader);
setCurrentItemInternal(ss.position, false, true);
} else {
mRestoredCurItem = ss.position;
mRestoredAdapterState = ss.adapterState;
mRestoredClassLoader = ss.loader;
}
}
由代码可知,Viewpager 在 save 时主要存储了 mAdapter.saveState();
敲黑板 此时可以复习下 save 和 restore 的调用时机和注意点,具体查看 onSaveInstanceState执行时机
//FragmentStatePagerAdapter.java
@Override
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i=0; i keys = bundle.keySet();
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);
}
}
}
}
}
saveState
主要是将 fragment.mIndex
存储到 bundle 中,然后通过 mFragmentManager.getFragment(bundle, key);
来进行 fragment 的恢复
如果有兴趣,可以继续看 Fragment.java-->void restoreChildFragmentState(@Nullable Bundle savedInstanceState) {
4 .解决方式
复写 FragmentStatePagerAdapter.saveState()
public class TestAdapter extends FragmentStatePagerAdapter {
@Override
public Parcelable saveState() {//空实现,不传数据
return null;
}
}