在Android中旋转设备后,Fragment字段为NULL



当我启动应用程序时,一切正常,但当我旋转到横向时,它会崩溃,因为在Fragment中有一个字段是NULL

我不使用setRetainInstance(true)或将碎片添加到FragmentManager。我在应用程序启动和旋转时创建新的碎片。

ActivityOnCreate()中,我创建了Fragment,并像这样将它们添加到viewPager中。

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ParentBasicInfoFragment parentBasicInfoFragment = new ParentBasicInfoFragment();
ParentUTCFragment parentUTCFragment = new ParentUTCFragment();
ParentEventsFragment parentEventsFragment = new ParentEventsFragment();
this.mFragments = new ArrayList<>();
this.mFragments.add(parentBasicInfoFragment);
this.mFragments.add(parentUTCFragment);
this.mFragments.add(parentEventsFragment);
this.viewpage.setOffscreenPageLimit(3);
setCurrentTab(0);
this.viewpage.setAdapter(new MainActivityPagerAdapter(getSupportFragmentManager(), this.mFragments));
}

然后我在应用程序上有一个测试按钮,当我按下它时,它会像一样

public void test(View view) {
((BaseFragment) MainActivity.this.mFragments.get(MainActivity.this.viewpage.
getCurrentItem())).activityNotifiDataChange("hello");
}

这将起作用,并且ViewPager中的当前Fragments具有正在调用的方法activityNotifiDataChange(),并且一切正常

当我旋转应用程序并按下按钮做同样的事情时,activityNotifiDataChange()被调用正常,但有一个空指针异常,因为ArrayList<Fragment> mFragment现在为空。

下面是一个展示这种行为的Android Studio小示例项目:https://drive.google.com/file/d/1Swqu59HZNYFT5hMTqv3eNiT9NmakhNEb/view?usp=sharing

启动应用程序并按下名为"press TEST"的按钮,然后旋转设备并再次按下按钮,观看应用程序崩溃

更新解决方案,感谢@GregMoens和@EpicPandaForce

public class MainActivityPagerAdapter extends PersistenPagerAdapter<BaseFragment> {
private static int NUM_ITEMS = 3;
public MainActivityPagerAdapter(FragmentManager fm) {
super(fm);
}
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ParentBasicInfoFragment.newInstance(0, "Page # 1");
case 1:
return ParentUTCFragment.newInstance(1, "Page # 2");
case 2:
return ParentEventsFragment.newInstance(2, "Page # 3");
default:
return null;
}
}
}
public abstract class PersistenPagerAdapter<T extends BaseFragment> extends FragmentPagerAdapter {
private SparseArray<T> registeredFragments = new SparseArray<T>();
public PersistenPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
@Override
public T instantiateItem(ViewGroup container, int position) {
T fragment = (T)super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public T getRegisteredFragment(ViewGroup container, int position) {
T existingInstance = registeredFragments.get(position);
if (existingInstance != null) {
return existingInstance;
} else {
return instantiateItem(container, position);
}
}
}

我在您的应用程序中看到的主要问题是您对FragmentPagerAdapter的工作方式存在误解。我经常看到这种情况,这是由于类中缺少好的javadocs。应该实现适配器,以便getItem(position)在调用时返回一个新的片段实例。然后,只有当寻呼机需要一个新的位置实例时,它才会调用getItem(position)。您不应该预先创建片段,然后将其传递到适配器中。您也不应该持有对来自活动或父片段(如ParentBasicInfoFragment)的片段的强引用。因为请记住,片段管理器是在管理片段,您也在通过新建片段并保留对它们的引用来管理片段。这导致了冲突,并且在旋转之后,您正试图对实际未初始化的片段调用activityNotificationDataChange()(未调用onCreate())。使用调试器和跟踪对象ID将确认这一点。

如果您更改代码,使FragmentPagerAdapter在需要时创建片段,并且不存储对片段或片段列表的引用,您将看到更好的结果。

this.mFragments = new ArrayList<>();

因为这是错误的。如果您使用ViewPager,则永远不应该持有对片段列表的引用。只需从FragmentPagerAdaptergetItem(int position)返回Fragment的新实例,就可以继续了。

但要修复代码,必须完全删除mFragments

请参阅https://stackoverflow.com/a/58605339/2413303了解更多详细信息。

使用setRetainInstance(true)不是一个好方法。如果你需要一些简单的信息,比如:recyclerView的位置,recyclerView的选定项目,也许一些模型(Parcelable),你可以用方法onSaveInstanceState/onRestoreInstanceState来做,有一个限制是1MB。下面是一个动画的例子。

为了获得更持久的持久性,请使用SharedPreferences、Room(Google ORM),或者您可以尝试将ViewModel与LiveData一起使用(最好是在用户会话期间使用一些数据)。

//change in AndroidManifest.xml file,
<activity
android:name=".activity.YourActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="sensor"
/>
//YourActivity in which you defines fragment.
//may be it helps

最新更新