Fragment使用的常见错误分析

Fragment使用的常见错误分析

在实现一个页面可以滑动子页面切换的功能时候,常常会使用ViewPager + FragmentPagerAdapter来实现。在xml 配置文件使用ViewPager控件,为其设置一个FragmentPagerAdapter,之后创建不同的Fragment 来显示子页面布局,这也就是比较大众的做法。下面讲述几种容易犯的错误。

1)在Fragment成员化时候,不觉意很容易犯一种错误,就是在onCreate 内创建好Fragment 后直接把它保存到成员变量数组内。大概如下:

public class MainActivity extends Activity
{
    private static final int COUNT = 3;
    List<Fragment> mFragments = new ArrayList<>(COUNT);
    ViewPager mViewPager;
    // 其他代码 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        // 其他代码 
        mViewPager = (ViewPager) findViewById(R.id.vp_container); 
        mFragments.add(Fragment_1.newInstance()); 
        mFragments.add(Fragment_2.newInstance()); 
        mFragments.add(Fragment_3.newInstance()); 
        mViewPager.setAdapter(new PagerAdapter(getSupportFragmentManager())); 
        // 其他代码
    } 
}

PagerAdapter 的代码大概如下(该类是上面类的内部类,方便阅读而单独抽取出来):

class PagerAdapter extends FragmentPagerAdapter {
    @Override
    public Fragment getItem(int position) {
        return mFragments[position];
    }

    @Override
    public int getCount() {
        return COUNT;
    }
}

Android 有个机制是在资源紧张的情况下,会把一些后台程序资源回收。上述代码,Activity在资源不紧张情况下没有被回收,这样使用是没有问题。但是Activity 一旦被回收(app 切换到后台,资源紧张情况下很常会这样,也可以在开发者选项下打开“不保留活动”,之后把app 切换到后台再次打开即可模拟),之后再重建,那么很可能就是崩溃bug 了。

崩溃原因,在Activity 被回收情况下再次打开app,那么 Activity 的onCreate 会被重新跑一遍,但是PagerAdapter的getItem 不会重新跑一遍的,这样就出现问题了。显示的Fragment 已经不是mFragments.add 内进去的那个Fragment了。因为Activity 在重建过程中,之前的Fragment也会被回收重建,新重建显示的Fragment 并没有走Fragment.newInstance() 创建,而是在Activity 的onCreate内的super.onCreate 被重建了,显示上使用的就是(super.onCreate 内创建的)这个。这样引起的问题就是,显示的Fragment 和Activity成员的mFragment是不同一套的数据了,其他地方使用mFagment 引用到时候,就引起其他逻辑的错误。

下图是就是模拟回收情况下的测试,可以看到Fragment 在很底层的super.onCreate 下调用的其他函数内被创建。

解决该问题,正确的做法应该如下,

public class MainActivity extends Activity
{
    List<Fragment> mFragments = new ArrayList<>(COUNT);
    ViewPager mViewPager;
    // 其他代码 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        // 其他代码 
        for (int i = 0; i < COUNT; i++) {
            mFragments.add(null); // 先填充为null
        }
        // 其他代码
    }
}

PagerAdapter 是MainActivity 的内部类,为方便阅读而抽取出来:

class PagerAdapter extends FragmentPagerAdapter {
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment object = (Fragment) super.instantiateItem(container, position);
        mFragments.set(position, object);
        return object;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
        mFragments.set(position, null);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) { 
            case 0:
                return Fragment_1.newInstance(); 
            case 1:
                return Fragment_2.newInstance();
            case 2:
                return Fragment_3.newInstance();
            default:
                return null;
        }
    }
    
    @Override
    public int getCount() {
        return COUNT;
    }
} 

在  Adapter的instantiateItem时候在去填充mFragments,destroyItem时候及时把它清理掉,这样就保证了显示的Fragment 和 mFragments 是同一组数据了。

2)还有一种比较容易犯的错误,其实和上面是类似的,只是做法不一样,大概如下:

下面mFragmentX 和mFragmentY 是Activity 的成员变量

class PagerAdapter extends FragmentPagerAdapter {
    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                mFragmentX = Fragment_X.newInstance();
                return mFragmentX;
            case 1:
                mFragmentY = Fragment_Y.newInstance();
                return mFragmentY;
            default:
                return null;
            }
        }
}

这个错误原因和上面描述的mFragments 数组使用的是一样的。

3 )在Activity 使用单个Fragment时候,很可能会在Activity 的onCreate 时候这样写:

@Override 
protected void onCreate(Bundle savedInstanceState) {
    mFragment = FragmentX.newInstance();
    getSupportFragmentManager().beginTransaction().replace(R.id.fl_container, mFragment).commit();
}

正确姿势可以这样:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mFragment = null;
    if (savedInstanceState != null) {
        mFragment = getSupportFragmentManager().findFragmentById(R.id.fl_container);
    }

    if (mFragment == null) {
        mFragment = Fragment.newInstance();
        getSupportFragmentManager().beginTransaction().add(R.id.fl_container, mFragment);
    }
}

上面代码判断savedInstanceState 不为null ,就是使得在Activity 被回收重建时候, 通过tag 获取到了底层已经重建的那个Fragment 了,这样就无需自己在创建了。

(全文完)

(欢迎转载本站文章,但请注明作者和出处 云域 – Yuccn

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注