复用已inflate的Fragment报IllegalStateException问题咨询
Fragment静态视图缓存高版本兼容性问题
优化背景
因Fragment视图渲染开销大、inflate耗时较长,我对项目中数量较少的Fragment做缓存处理以优化加载性能。
每个Fragment类中定义静态变量存储缓存的视图实例:
static View currentView = null;
在Fragment的onCreateView回调中对该变量赋值,仅首次加载时执行inflate操作:
if (currentView == null) currentView = inflater.inflate(R.layout.fragment_menu, container, false);
在SDK 25及更低版本中,以下运行逻辑完全正常:
- 实例化
Fragment_proxydisplay_menu对象,调用AddItems方法传入菜单数据:
Fragment_proxydisplay_menu menu = new Fragment_proxydisplay_menu(); menu.AddItems("1","2");
- 调用FragmentManager展示该Fragment:
FragmentManager fragmentManager = myActivity.getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.content, menu, menu.getClass().getSimpleName()); fragmentTransaction.addToBackStack(null); fragmentTransaction.commitAllowingStateLoss(); fragmentManager.executePendingTransactions();
高版本异常表现
在新版SDK环境下,当菜单项触发子菜单、重复执行上述逻辑加载承载不同数据的同类型Fragment时,会抛出如下IllegalStateException异常:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. at android.view.ViewGroup.addViewInner(ViewGroup.java:5124) at android.view.ViewGroup.addView(ViewGroup.java:4953) at android.view.ViewGroup.addView(ViewGroup.java:4893) at androidx.fragment.app.FragmentStateManager.addViewToContainer(FragmentStateManager.java:840) at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:529) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261) at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1890) at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1808) at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1751) at androidx.fragment.app.FragmentManager.executePendingTransactions(FragmentManager.java:614)
- 特殊复现规律:如果在两次菜单Fragment跳转中间插入一个简单的“请稍候”Fragment,就不会触发该异常,该问题在旧版SDK中从未出现。
已尝试方案及待解问题
我参考公开资料尝试在复用currentView前先将其从原有父容器移除:
if(currentView == null) currentView = inflater.inflate(R.layout.fragment_proxydisplay_menu, container, false); else ((ViewGroup) currentView.getParent()).removeView(currentView);
但该方案会导致其他Fragment展示白屏,无法正常使用。
由于项目中所有Fragment都是自包含的,我没有编写额外逻辑跟踪当前屏幕显示的Fragment,目前有两个核心疑问:
- 为什么新版SDK会出现这个旧版本不存在的异常?
- 如何在不需要感知当前展示页面的前提下,复用已经inflate完成的Fragment、传入新数据重绘,避免连续打开同类型页面报错?
编辑补充:新增测试结果
前述removeView复用方案导致其他Fragment白屏的具体原因暂未排查
我周末针对FragmentManager的执行逻辑做了对照测试,原有添加Fragment的代码逻辑保持不变:
FragmentManager fragmentManager = myActivity.getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.content, menu, menu.getClass().getSimpleName()); fragmentTransaction.addToBackStack(null); fragmentTransaction.commitAllowingStateLoss(); fragmentManager.executePendingTransactions();
测试发现,如果在replace/commit操作前,先通过tag查找已存在的同类型Fragment并提前remove,异常就会消失:
FragmentManager fragmentManager = myActivity.getSupportFragmentManager(); String tag = menu.getClass().getSimpleName(); // 新增以下三行代码后异常不再出现 Fragment currentFragment = fragmentManager.findFragmentByTag(tag); if(currentFragment != null && currentFragment.getClass().equals(tag)) fragmentManager.beginTransaction().remove(fragment).commit(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.content, menu, tag); fragmentTransaction.addToBackStack(null); fragmentTransaction.commitAllowingStateLoss(); fragmentManager.executePendingTransactions();
针对该测试结果,新增三个疑问:
- 为什么提前remove同tag Fragment可以解决异常问题?
- 这个方案是真正解决了根因还是只是掩盖了问题?
- 按FragmentManager的设计逻辑,replace操作本身不就等价于remove+add吗?
内容的提问来源于stack exchange,提问作者Lonko
相关产品推荐
相关产品推荐

