FragmentPagerAdapter 的那些坑

来源:互联网 发布:陕西大数据集团官网 编辑:程序博客网 时间:2024/06/05 16:03

引子

image

开始前,首先来看一下这种常用的布局,每个底部导航栏项中呢都有一个ViewPager。
笔者先来说一下自己遇到的问题,第一次进入的时候ViewPager(第一页)显示正常,但是切换到别的底部tab再切换回来的时候,ViewPager的第一项和第二项的视图就没有了,试过FragmentPagerAdapter 和 FragmentStatePagerAdapter 但都没有解决问题,所以笔者决定从源码的角度出发,去探索问题所在。

起始

首先
底部Tab1是HomeFragement
底部Tab2是OrdersFragment

由HomeFragment切换到OrdersFragment之后的周期调用情况如下:

— 后台打印如下 —
07-29 11:21:56.560 18830-18830/com.finobusiness.forhomelife D/HomeFragment: onDestroyView:
07-29 11:21:56.565 18830-18830/com.finobusiness.forhomelife D/OrdersFragment: onCreateView:

HomeFragment只是调用了onDestroyView方法,没有调用onDestroy方法。

问题源头

FragmentPagerAdapterinstantiateItem()方法如下:

 @Override    public Object instantiateItem(ViewGroup container, int position) {        final long itemId = getItemId(position);        // Do we already have this fragment?        String name = makeFragmentName(container.getId(), itemId);        Fragment fragment = mFragmentManager.findFragmentByTag(name);        if (fragment != null) {            mCurTransaction.attach(fragment);        } else {            fragment = getItem(position);            mCurTransaction.add(container.getId(), fragment,                    makeFragmentName(container.getId(), itemId));        }        return fragment;    }

需要注意的是这段代码:

        Fragment fragment = mFragmentManager.findFragmentByTag(name);

可以看到在去实例化一个Fragment之前会先去FragmentManager当中通过Tag查找是否已经有了这个Fragment,如果有,则不再调用public abstract Fragment getItem(int position) 这个方法,通常这是咱们实现的用来提供Fragment的方法。

Fragment 的 onDestroyView()

因为ViewPager当中的Fragment的视图已经被销毁了

onDestroyView的源码

    public void onDestroyView() {        mCalled = true;    }

没错,只有这么一行,但是里面的mCalled威力可是很惊人的,一会儿就会涉及到。
在代码中查询,发现onDestroyView()方法在全局中只有一个地方被调用到了,那就是

 void performDestroyView() {        if (mChildFragmentManager != null) {            mChildFragmentManager.dispatchDestroyView();        }        mState = CREATED;        mCalled = false;        onDestroyView();        if (!mCalled) {            throw new SuperNotCalledException("Fragment " + this                    + " did not call through to super.onDestroyView()");        }        if (mLoaderManager != null) {            mLoaderManager.doReportNextStart();        }    }

首先呢可以看到如果onDestroyView()方法在在子类中没有被调用,则会抛出SuperNotCalledException异常

其次

mChildFragmentManager.dispatchDestroyView 最终会调用到FragmentManagervoid moveToState(int newState, int transit, int transitStyle, boolean always)方法。

moveToState方法的源码如下

    void moveToState(int newState, int transit, int transitStyle, boolean always) {        mCurState = newState;        if (mActive != null) {            boolean loadersRunning = false;            for (int i=0; i<mActive.size(); i++) {                Fragment f = mActive.get(i);                if (f != null) moveToState(f, newState, transit, transitStyle, false);            }        }    }
    case Fragment.ACTIVITY_CREATED:            if (newState < Fragment.ACTIVITY_CREATED) {                f.performDestroyView();                if (f.mView != null && f.mContainer != null) {                    f.mContainer.removeView(f.mView);                }                f.mContainer = null;                f.mView = null;                f.mInnerView = null;    }

最终可以看到fragment.view被移除并且被置为null。
下一次进来的时候Fragment被通过getFragmentByTag方法找到,但是他的View却是空,所以我们的ViewPager的前两页(默认的缓存页)就没有视图。

解决之道

解决的办法就是ViewPager所在的那个Fragment中手动调用PagerAdapter 的destroyItem方法

0 0