引子
开始前,首先来看一下这种常用的布局,每个底部导航栏项中呢都有一个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方法。
问题源头
FragmentPagerAdapter
的instantiateItem()
方法如下:
@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
最终会调用到FragmentManager
的void 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
方法