Fragment嵌套Fragment时正确的获取FragmentManager

在使用了Google MVP architecture之后,View层以一个动态Fragment的形式独立于Activity。在原本使用Activity作为View层时,如果需要内嵌多个动态Fragment,例如常见的ViewPager,我们可以使用Activity的FragmentManager来管理他们。而在Google MVP模式下,这就变成了一个View层的主Fragment嵌套许多子Fragment。在这时,我们应相应的用View层Fragment的FragmentManager来管理子Fragment。而这时,获取FragmentManager的方法应为getChildFragmentManager(),注意这里容易写错成getActivity().getSupportFragmentManager(),如果使用后者,则获得的是外层Activity的FragmentManager,此时是不符合逻辑的。

例如PKU Helper 3.0+版本的树洞页面,HoleFragment为整体的View层,其中只包含一个ViewPager。ViewPager包含树洞关注两个HoleListFragment。

树洞截图

在HoleFragment中,有如下setupPager()方法,在onCreateView()中被调用,负责初始化ViewPager。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void setupPager() {
List<Fragment> fragments = new ArrayList<>();
if (getActivity().getSupportFragmentManager().getFragments() != null &&
!getActivity().getSupportFragmentManager().getFragments().isEmpty()) {
fragments = getActivity().getSupportFragmentManager().getFragments();
mMainHolesViewFragment = (HoleListFragment) fragments.get(HoleContract.HolesView.POSITION_MAIN);
mAttentionHolesViewFragment = (HoleListFragment) fragments.get(HoleContract.HolesView.POSITION_ATTENTION);
}
else {
mMainHolesViewFragment = HoleListFragment.newInstance(HoleContract.HolesView.POSITION_MAIN);
mAttentionHolesViewFragment = HoleListFragment.newInstance(HoleContract.HolesView.POSITION_ATTENTION);
fragments.add(mMainHolesViewFragment);
fragments.add(mAttentionHolesViewFragment);
}
mMainHolesViewFragment.setPresenter(mPresenter);
mAttentionHolesViewFragment.setPresenter(mPresenter);

mViewPager.setAdapter(new HolePagerAdapter(getActivity().getSupportFragmentManager(), fragments));
}

如果按以上代码编写,在重加载HoleActivity时,会弹出NullPointer异常。追根溯源,是重加载时初始化的Fragment并非是15、16行中与Presenter绑定的Fragment,即他们是不同的实例,这在debug模式中查看内存地址可以得到。于是在Presenter调用View层,即子Fragment中的方法时,调用的是未初始化的Fragment,其又含有未赋值的成员变量,故在调用这些成员变量时,触发了NullPointer异常。

正确的方法是,将上述代码中全部的getActivity().getSupportFragmentManager()替换为getChildFragmentManager(),这时我们才获取的是正确的FragmentManager。

抽象图

在上图中我们可以更加清晰的看到,负责管理ViewPager中的两个子Fragment的应当是FragmentManager#2,所以用getChildFragmentManager()才是正确的做法。