如何从savedInstanceState中恢复ViewPager

9
我有一个问题,似乎无法解决。在我的一个活动(即MainActivity)中,我有一个ViewPager。 我正在实现所有必需的方法来保存和检索实例状态,以便在后台杀死该活动时使用。但当活动尝试恢复其状态时,片段被恢复,但它们未附加到viewpager上,因此我得到的只是白屏。
以下是相关代码:
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //..
    if (savedInstanceState==null) {
        setupViewPager();
    }
}

private void setupViewPager() {
    mAdapter = new ViewPagerAdapter(getSupportFragmentManager());
    mAdapter.addFrag(FirstFragment.newInstance(mFeedItem), "INFO");
    //Note: mFeedItem is obtained from the intent and I have verified
    //      that it is properly restored.
    mAdapter.addFrag(SecondFragment.newInstance(mFeedItem), "SERVICES");
    mViewPager.setAdapter(mAdapter);
    mTabLayout.setupWithViewPager(mViewPager);
}

ViewPagerAdapter.java

public class ViewPagerAdapter extends FragmentStatePagerAdapter {
    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

    public ViewPagerAdapter(FragmentManager manager) {
        super(manager);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }

    public void addFrag(Fragment fragment, String title) {
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mFragmentTitleList.get(position);
    }
}

FirstFragment.java

public static FirstFragment newInstance(Workshop item) {
    FirstFragment f = new FirstFragment();
    f.mItem = item;
    return f;
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString("feed_item",Workshop.packToGson(mItem));
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_item_detail_info, container, false);
    if (savedInstanceState!=null) {
        mItem = Workshop.extractGson(savedInstanceState.getString("feed_item"));
    }
    return v;
}

如果我导航到另一个活动,然后在后台杀死MainActivity(我使用不保留活动设置来测试这个问题),会发生以下情况:

1) 活动被正确恢复,没有任何问题。
2) 片段的onCreateView被调用,并且包含savedInstanceState
3) 然而,在我的活动中,ViewPager和Adapter为空。片段似乎没有重新附加到适配器/ViewPager上。

那么我错过了什么?我需要做什么才能正确地恢复这些片段吗?

PS:

我可以快速解决这个问题,只需在我的MainActivity中进行以下替换:

if (savedInstanceState==null) {
    setupViewPager();
}

只需使用 setupViewPager();,无论是恢复还是首次启动,都将创建新的适配器和片段。 但这带来了重置片段状态的缺点。 另外,当我这样做时,我发现每个片段都有两个实例,一个是恢复的,一个是新的,只有新实例才会显示。 因此,我不认为这是最佳方法。

2个回答

7
通过销毁Activity,您还会破坏每个实例字段。因此,您需要重新实例化ViewPagerAdapter。您需要记住两种情况:
  1. FragmentStatePagerAdapter使用FragmentManager保存/恢复Fragments
  2. 您必须恢复Fragment内部的Fragments状态
第一点是为什么每个Fragment都有两个实例的原因。为了解决这个问题,您可以通过其ID获取存储的Fragment。请参考这个grepcode FragmentPagerAdapter(同样适用于StateAdapter)。
private static String makeFragmentName(int viewId, int index) {
    return "android:switcher:" + viewId + ":" + index;
}

不要每次在setupViewPager中实例化一个Fragment,而是首先从FragmentManager获取它。

mAdapter = new ViewPagerAdapter(getSupportFragmentManager());
int fragmentCount = 2; // save value if it's a arbitrary list of fragments
for (int index = 0; i < fragmentCount; i++) {
    FirstFragment firstFragment = (FirstFragment) getSupportFragmentManager().findFragmentByTag(makeFragmentName(R.id.view_pager, index));
    if (firstFragment == null) {
        firstFragment = FirstFragment.newInstance(mFeedItem);
    }
    mAdapter.addFrag(firstFragment, "someTitle");
}

上面是一个示例,说明如何从FragmentManager中恢复之前保存的Fragments。您可能需要调整不同类型的BaseFragments等情况。此外,您可能希望保存Fragment标题名称和总数以进行恢复。
第二点是您必须从Fragment本身恢复当前Fragment状态。为此,您的Fragment具有生命周期方法。请参见此SO主题(一劳永逸)如何正确保存后退堆栈中的Fragments实例状态?

我尝试过这样做,但是“findFragmentByTag”调用始终为空。 - ShahiM
如果您正确地执行了它,那么这意味着Fragment一开始并没有被实例化/保存。然后您需要使用if-branch来实例化一个新的。 - Murat Karagöz
似乎对于FragmentStatePagerAdapter不起作用。我将其更改为FragmentPagerAdapter,一切都很好。 - ShahiM
我理解了这些,因为我遇到了同样的问题。将FragmenStatePagerAdapter更改为FragmentPagerAdapter后它就可以工作了,但是添加到ViewPager中的片段是不可见的,就像没有被充气一样,即使调用了onViewCreated方法也是如此。我不明白为什么会这样。 - poetryrocksalot

3
FragmentStatePagerAdapter有saveState()和restoreState()方法来保存和恢复它的状态。

https://developer.android.com/reference/android/support/v4/app/FragmentStatePagerAdapter.html#saveState()

saveState:保存与此适配器及其页面关联的任何实例状态,如果需要重构当前 UI 状态,则应恢复该状态。因此,似乎可以使用这些方法来实现您的目标。很抱歉,我现在没有编译器,所以无法测试下面的代码。
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState == null) {
        setupViewPager(savedInstanceState);
    }
}

private void setupViewPager(Bundle savedInstanceState) {
    mAdapter = new ViewPagerAdapter(getSupportFragmentManager());

    if (savedInstanceState != null) {
        Parcelable parcelable = savedInstanceState.getParcelable("Adapter");
        mAdapter.restoreState(parcelable, getClassLoader());
    } else {
        mAdapter.addFrag(FirstFragment.newInstance(mFeedItem), "INFO");
        //Note: mFeedItem is obtained from the intent and I have verified
        //      that it is properly restored.
        mAdapter.addFrag(SecondFragment.newInstance(mFeedItem), "SERVICES");
    }
    mViewPager.setAdapter(mAdapter);
    mTabLayout.setupWithViewPager(mViewPager);
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    Parcelable parcelable = mAdapter.saveState();
    outState.putParcelable("Adapter", parcelable);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接