一直以来使用ViewPager和FragmentPagerAdapter或FragmentStatePagerAdapter管理Fragment的错误

  • Post author:
  • Post category:其他


一直以来我在用viewpager和FragmentPagerAdapter或FragmentStatePagerAdapter管理fragment的时候总是和下面的代码类似,但是下面的代码在某些情况下会出现问题

接下来我们慢慢分析:

public class MainActivity extends FragmentActivity {
    private ViewPager m_vp;
    private ArrayList<Fragment> fragmentList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        m_vp = (ViewPager)findViewById(R.id.viewpager);

        mfragment1 = new fragment1();
        mfragment2 = new fragment2();
        mfragment3 = new fragment3();

        fragmentList = new ArrayList<Fragment>();
        fragmentList.add(mfragment1);
        fragmentList.add(mfragment2);
        fragmentList.add(mfragment3);


        m_vp.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager()));
    }

    public class MyViewPagerAdapter extends FragmentPagerAdapter{

        @Override
        public Fragment getItem(int arg0) {
            return fragmentList.get(arg0);
        }

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

什么时候会出现问题:

在activity非正常情况下销毁重建的时候例如:屏幕旋转、内存不足在后台被回收。

会出现什么问题和原因:

当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态

两种Adapter的问题还不一样:如果这两种Adapter的本质区别不知道的可以看我这篇写了一点自己的理解:

Fragment的懒加载和三种Adapter的对比

1.FragmentPagerAdapter:因为会缓存所有的已加载过的fragment,所以如果出现非正常情况下activity被回收重建,上面的写法会重新创建这些fragment,但是因为viewpager和fragmentPagerAdapter的机制,之前已经加载过的fragment并不会被新创建的fragment替换掉,这时如果我们发起Activity和fragment的通信例如调用fragemt1.setxxx方法就会crash或者没有效果,因为调用的是新创建的fragment的方法,这样系统里其实在维护着两套fragmentlist,而且真正起作用的那一套用我们上面的代码就获取不到,

如果应用中并没有类似activity调用之前fragment的方法貌似不会出问题但是不能因为侥幸避开了问题就对问题视而不见。

为什么我们新创建的fragment没有代替旧的fragment原因实质代码:

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + (fragment.getView()));
        //***下面代码仅仅销毁了view并没有从fragmentManager中移除fragment
        mCurTransaction.detach(fragment);
        if (fragment == mCurrentPrimaryItem) {
            mCurrentPrimaryItem = null;
        }
    }    

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        //***如果是已经加载过一次的fragment,在重建后能够通过下面这个方法再次获取到
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        
        //***看下面的代码fragment已经存在就不走getItem方法,这样我们上面的fragmentList的 
        //fragment就没有用上 其实质就是因为fragmentManager保存了我们的fragment, 
        //在activity重建后并没有调用getItem方法
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
            } else {
                fragment.setUserVisibleHint(false);
            }
        }

        return fragment;
    }

2.FragmentStatePagerAdapter:在正常情况下不会缓存已经销毁的fragment,但是如果出现非正常情况下的activity被回收重建,正在显示的和已经预加载的这几个fragment会出现和上面同样的问题实质代码:

FragmentStatePagerAdapter在destroyItem方法中彻底的移除了fragment按理说不会出现上述问题,但是由于viewpager和fragmentStateAdapter的处理activity异常销毁代码:

在出现这种异常销毁activity并且重建的时候会保存之前的状态数组和正在显示以及预加载的fragment

//这是viewpager中的代码
    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.position = mCurItem;
        if (mAdapter != null) {
            ss.adapterState = mAdapter.saveState();
        }
        return ss;
    } 

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        if (mAdapter != null) {
            mAdapter.restoreState(ss.adapterState, ss.loader);
            setCurrentItemInternal(ss.position, false, true);
        } else {
            mRestoredCurItem = ss.position;
            mRestoredAdapterState = ss.adapterState;
            mRestoredClassLoader = ss.loader;
        }
    }


//下面是FragmentStatePagerAdapter中的代码
@Override
    @Nullable
    public Parcelable saveState() {
        Bundle state = null;
        if (mSavedState.size() > 0) {
            state = new Bundle();
            Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
            mSavedState.toArray(fss);
            state.putParcelableArray("states", fss);
        }
        for (int i=0; i<mFragments.size(); i++) {
            Fragment f = mFragments.get(i);
            if (f != null && f.isAdded()) {
                if (state == null) {
                    state = new Bundle();
                }
                String key = "f" + i;
                mFragmentManager.putFragment(state, key, f);
            }
        }
        return state;
    }

    @Override
    public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
        if (state != null) {
            Bundle bundle = (Bundle)state;
            bundle.setClassLoader(loader);
            Parcelable[] fss = bundle.getParcelableArray("states");
            mSavedState.clear();
            mFragments.clear();
            if (fss != null) {
                for (int i=0; i<fss.length; i++) {
                    mSavedState.add((Fragment.SavedState)fss[i]);
                }
            }
            Iterable<String> keys = bundle.keySet();
            for (String key: keys) {
                if (key.startsWith("f")) {
                    int index = Integer.parseInt(key.substring(1));
                    Fragment f = mFragmentManager.getFragment(bundle, key);
                    if (f != null) {
                        while (mFragments.size() <= index) {
                            mFragments.add(null);
                        }
                        f.setMenuVisibility(false);
                        mFragments.set(index, f);
                    } else {
                        Log.w(TAG, "Bad fragment at key " + key);
                    }
                }
            }
        }
    }

又因为:

 @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        //***这里的mFragments在activity重建的时候调用的restoreState方法中会还原销毁之前的
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        //***这里虽然直接执行了getItem貌似fragmentManager中的fragment总是我们正在控制的 
        //fragment,请看上面代码
        Fragment fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }
        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
            fragment.setUserVisibleHint(false);
        }

        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);

        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        }

        return fragment;
    }


总结: 由于我们的错误写法,在activity被异常的销毁重建的时候系统中出现了两套FragmentList而且真正显示的fragmentList我们并没有维护


解决方案:1.通过处理不让代码中出现两套


2.获取系统为我们保存的fragment并且维护起来

如何解决:

1.


只适用与FragmentPagerAdapter:



这样就能保证fragmentManager中控制的fragmentList和我们activity中维护的fragmentList是同一个

//原因代码
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
    private static String makeFragmentName(int viewId, long id) {
        return "android:switcher:" + viewId + ":" + id;
    }

//下面是解决代码
private Fragment instantiateFragment(ViewPager viewPager, int position, Fragment defaultResult) {
    String tag = "android:switcher:" + viewPager.getId() + ":" + position;
    Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
    return fragment == null ? defaultResult : fragment;
}

        fragmentList = new ArrayList<>();
        mfragment1 = instantiateFragment(viewpager,0,FirstFragment.newInstance());
        mfragment2 = instantiateFragment(viewpager,1,SecondFragment.newInstance());
        mfragment3 = instantiateFragment(viewpager,2,ThirdFragment.newInstance());
        fragmentList.add(mfragment1);
        fragmentList.add(mfragment2);
        fragmentList.add(mfragment3);

2.两种Adapter都适用的解决办法:

 private SparseArray<Fragment> sparseArray = new SparseArray<>();
    public class ZHYFragmentPagerAdapter extends FragmentStatePagerAdapter {

        public ZHYFragmentPagerAdapter(@NonNull FragmentManager fm, int behavior) {
            super(fm, behavior);
        }

        @NonNull
        @Override
        public Fragment getItem(int position) {
            //这样写避免了因为activity异常销毁重建系统中出现两个同样的Fragment的实例
            //接下来就是如何获取之前系统为我们存下的fragment并且维护到我们的代码里
            Fragment fragment;
            switch (position){
                case 0:
                    fragment = FirstFragment.newInstance();
                    break;
                case 1:
                    fragment = SecondFragment.newInstance();
                    break;
                case 2:
                    fragment = ThirdFragment.newInstance();
                    break;
                case 3:
                    fragment = FourthFragment.newInstance();
                    break;
                default:
                    fragment = FirstFragment.newInstance();
                    break;
            }
            return fragment;
        }

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

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            //因为不管是重建还是第一次创建都会调用instantiateItem而且和FragmentManager中维护的是同一个
            // 我们在这里获取要维护的fragment
            Fragment fragment = (Fragment) super.instantiateItem(container, position);
            sparseArray.put(position,fragment);
            return fragment;
        }

        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            sparseArray.remove(position);
            super.destroyItem(container, position, object);
        }
    }



版权声明:本文为m0_37707561原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。