Android切换深浅色模式时ViewPager2内嵌Fragment根视图引用丢失问题
问题根因
- 深浅色切换属于Android系统配置变更,默认会触发Activity及所有依附的Fragment销毁重建,你代码中多处错误的缓存逻辑导致后续操作的都是销毁前的旧Fragment实例,其绑定的根视图已经被回收,因此出现引用丢失问题。
- 具体错误点:
- 把
viewPager、pagerAdapter声明为static,配置变更后依然持有旧实例,造成内存泄漏同时逻辑异常 - 在
MyPagerAdapter中提前缓存3个子Fragment实例,配置变更后系统会自动重建新的子Fragment,你缓存的旧实例早已和视图解绑 - 调用
pagerAdapter.createFragment()获取子Fragment实例,这个方法的作用是创建新的Fragment实例,不是获取当前已经 attach 到页面的正在显示的实例,拿到的永远是你提前缓存的无效旧实例
- 把
修复步骤
1. 移除错误的static声明和Fragment缓存逻辑
主Fragment中删除viewPager、pagerAdapter的static修饰符,修改MyPagerAdapter不要提前缓存子Fragment:
// 原错误static声明修改为普通成员变量 public ViewPager2 viewPager; public MyPagerAdapter pagerAdapter; public static class MyPagerAdapter extends FragmentStateAdapter { public MyPagerAdapter(FragmentActivity fa) { super(fa); } @NonNull @Override public Fragment createFragment(int pos) { // 每次创建新实例即可,FragmentStateAdapter会自行管理生命周期 switch (pos) { case 0: return DisplayScoresFragment.newInstance(1, false); case 1: return DisplayScoresFragment.newInstance(2, false); case 2: return DisplayScoresFragment.newInstance(3, false); default: return DisplayScoresFragment.newInstance(1, false); } } @Override public int getItemCount() { return NUM_PAGES; } }
2. 新增从FragmentManager获取当前存活子Fragment的方法
不要调用createFragment拿实例,从getChildFragmentManager中获取系统自动重建的当前正在显示的子Fragment:
// 主Fragment新增方法 private DisplayScoresFragment getFragmentAtPosition(int position) { // ViewPager2的FragmentStateAdapter默认生成的Fragment tag格式为f + itemId return (DisplayScoresFragment) getChildFragmentManager().findFragmentByTag("f" + pagerAdapter.getItemId(position)); }
3. 修改setAllChecked方法,使用正确的实例获取逻辑
public void setAllChecked(boolean v) { for (int i = 0; i < NUM_PAGES; i++) { DisplayScoresFragment fragment = getFragmentAtPosition(i); // 实例为空说明还没初始化,跳过即可,后续初始化时会自动读取最新状态 if (fragment != null) { fragment.check = v; fragment.reloadModes(false); } } viewPager.setCurrentItem(viewPager.getCurrentItem()); }
4. 子Fragment增加空判断避免崩溃
子Fragment的reloadModes方法增加视图有效性判断,避免视图未初始化时调用报错:
public void reloadModes() { if (root != null && root.isAttachedToWindow()) { init(root, mode, true); } } public void reloadModes(boolean v) { if (root != null && root.isAttachedToWindow()) { init(root, mode, v); } } // 可选优化:在onDestroyView中把root置空,避免持有旧视图引用 @Override public void onDestroyView() { super.onDestroyView(); root = null; pullToRefresh = null; }
内容的提问来源于stack exchange,提问作者Jannis
相关产品推荐
相关产品推荐

