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 )