记一个ViewPager+Fragment的意外销毁重建的崩溃问题

1.问题描述

一般的Viewpager嵌套Fragment

如上图所示,布局为典型的Activity嵌套ViewPager再嵌套Fragment

在首次进入该页面和正常使用时没有任何问题,但是在Activity意外销毁时会出现空指针问题(也不一定是这个问题,后面会详细说明)造成崩溃

2.原因分析

  • 首先,正常使用没有问题则说明代码逻辑上没有太大的硬伤
  • 出现意外销毁时导致,则问题最有可能出现在保存和恢复状态期间
    kotlin.UninitializedPropertyAccessException: lateinit property mViewModel has not been initialized

具体的报错信息为上面的红字,这个是kotlin的lateinit属性在调用时没有初始化的Exception,可以理解为Java的空指针

发生在ViewPager.OnPageChangeListener.onPageSelected(),在这个回调中,调用了Fragment中的刷新方法,导致了崩溃

注释掉该行代码后,没有再次崩溃,而且Fragment可以正常使用(如刷新,点击等)

这就说明,Activity正常恢复,Fragment正常恢复,问题出现在ViewPager

在Activity中,使用了一个List来保存这些Fragment,在刚才的回调中会使用该list获取对应的Fragment进行操作

这时候就会有人说:list肯定没更新

但是往下看,在Activity重建时,同样也会调用到onCreate,那么在这个流程中该list与fragment都进行了重建,而且为ViewPager的Adapter进行了重新赋值

按一般思维来说,怎么着都不应该出问题,这些Fragment,Adapter啥的都是新的

但是看一下关于ViewPager恢复相关的代码

//ViewPager
@Override
public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();//这里的super是View,就是一般的现场保存
    SavedState ss = new SavedState(superState);
    ss.position = mCurItem;
    if (mAdapter != null) {
        ss.adapterState = mAdapter.saveState();
    }
    return ss;
}
  
//PagerAdapter
@Nullable
public Parcelable saveState() {
    return null;
}
//FragmentPagerAdapter
@Override
public Parcelable saveState() {
    return null;
}
  
//FragmentStatePagerAdapter,相比于FragmentPagerAdapter增加了对Fragment的状态保存
@Override
public Parcelable saveState() {
    Bundle state = null;
    ...
    for (int i=0; i

这段代码可以看出来,FragmentPagerAdapter没有对状态进行保存,而FragmentStatePagerAdapter将Fragment的状态保存到了FragmentManager中

而在创建Adapter时,是通过重写 getItem(int position); 方法进行Fragment的创建(普通ViewPager是通过instantiateItem(ViewGroup container, int position) 进行创建)

而通过源码可以知道,getItem同样也是在instantiateItem方法中进行的调用,如下(省略了无关代码)

//FragmentPagerAdapter,没有保存状态,所以也没有恢复状态
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
 
    final long itemId = getItemId(position);
 
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);//1
    if (fragment != null) {
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);//2
        ...
    }
    ...
 
    return fragment;
}
//FragmentStatePagerAdapter,恢复状态
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
    if (state != null) {
        Bundle bundle = (Bundle)state;
        bundle.setClassLoader(loader);
        ...
        mFragments.clear();
        ...
        Iterable 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) {
                    ...
                    mFragments.set(index, f);
                } ...
            }
        }
    }
}
  
  
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    if (mFragments.size() > position) {//21
        Fragment f = mFragments.get(position);
        if (f != null) {
            return f;
        }
    }
 
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
 
    Fragment fragment = getItem(position);//22
    ...
 
    return fragment;
}

3.解决办法

1.治标:设置configChanges的uiMode和screenSize等属性使Activity不在旋转屏幕和切换暗黑模式时重新构建,减少Activity的意外销毁(但是还是有可能在内存不足时进行销毁)

2.治本:既然FragmentManager管理了所有的Fragment,那么在使用时也通过该FragmentManager进行获取而不是通过list,如果想使用list那么应该在页面恢复时重新构建一个List,代码如下:

val fragments: List? = supportFragmentManager.fragments
val list = mutableListOf()
if (fragments == null || fragments.isEmpty()) {
    list.add(XXXFragment.newFragment(ListTypeXXX))
    list.add(XXXFragment.newFragment(ListTypeXXX))
    list.add(XXXFragment.newFragment(ListTypeXXX))
    list.add(XXXFragment.newFragment(ListTypeXXX))
    list.add(XXXFragment.newFragment(ListTypeXXX))
} else {
    for (fragment in fragments) {
        if (fragment is OrderFragment) {
            list.add(fragment)
        }
    }
}

你可能感兴趣的:(记一个ViewPager+Fragment的意外销毁重建的崩溃问题)