解决 ViewPager + Fragment 的恢复问题

你可能是这么使用 ViewPager + Fragment 这个组合的:



public class MainActivity extends AppCompatActivity {

    private ViewPager mViewPager;

    private List<Fragment> mFragments;
    private CustomViewPagerAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mViewPager = (ViewPager) findViewById(R.id.view_pager);

        mFragments = new ArrayList<>();
        mFragments.add(new SimpleFragment());
        mFragments.add(new SimpleFragment());
        mAdapter = new CustomViewPagerAdapter(getSupportFragmentManager(), mFragments);
        mViewPager.setAdapter(mAdapter);
    }

    public class CustomViewPagerAdapter extends FragmentStatePagerAdapter {
        private List<Fragment> mFragments;

        public CustomViewPagerAdapter(FragmentManager manager, List<Fragment> fragments) {
            super(manager);
            mFragments = fragments;
        }

        @Override
        public Fragment getItem(int position) {
            return mFragments == null ? null : mFragments.get(position);
        }

        @Override
        public int getCount() {
            return mFragments == null ? 0 : mFragments.size();
        }
    }

其中 SimpleFragment 就是一个普通的 Fragment,这里就不贴代码了。

正常情况使用,一般不会有什么问题——这里的正常情况,包括应用冷启动,然后你按 Home 键盘放到后台,然后再回来。但是有一种情况我们经常会忽视:当这个 Activity 被杀再次回来的情况。

一般来说这个情况在内存低的时候容易出现,毕竟内存少了被杀很正常,但是现在动辄大内存的 Android 设备上可能要重现这个有点困难——其实也不困难,到开发者选项里,把 “不保留活动” 开关打开就可以了,这时候再测试,嗯可能简单地使用并没有发现什么问题,但是没发现不代表就没有问题。

首先,Activity 的 onCreate 方法将会被再次调用,Fragment 的列表依然会再次初始化,两个 Fragment 都是重新 new 出来的。但是跟第一次启动的区别在于,savedInstanceState 这个变量不再是 null。同时,这种情况(不保留 Activity)下,Activity 是会被销毁的,但是 Fragment 却不会,所以就会导致有 Fragment 重叠的问题——当前显示屏上显示的 Fragment 并不是你当前 Activity 里的那个。

为了解决这个,可以从以下方面入手:

  • 重写 protected void onSaveInstanceState(Bundle outState) 方法,并在里面不做任何操作(不调 super 的方法);这样在 Activity 被销毁的时候,FragmentManager 里的 Fragment 也不会得到保留。
  • 在 onCreate 方法里,如果 savedInstanceState 不为 null,那么拿到之前的 Fragment,重新添加到列表里。

嗯,第二个方法有个问题:怎样拿到之前的 Fragment?

FramgentManager 有两个方法:

  • findFragmentByTag(String tag);
  • findFragmentById(int id);

看以上的代码 ,你并没有发现有任何地方给这些 Fragment 设置 Id/Tag 啊?嗯,事实上,你对这两个 Fragmente 输出一下的话,就可以看到,这两个 Fragment 的 id,就是这个 ViewPager 的 Id,然后 Tag 是 null。

你可能会说:等等,我看到 SO 里,说这个 Tag 可以这样得到的啊 "android:switcher:"+R.id.page+pos; 当我查阅源码的时候,发现 FragmentStatePagerAdapter 并没有给 Fragment 设置这么一个东西,估计是新版本的 Support 库(当前我使用的是 25.2.0)已经移除了这样的方法,所以得到的 Tag 是 null。

所以也别无他法了,只有使用第一个方法:重写 onSaveInstanceState 解决。