如何避免FragmentManager竞态条件,保证同个DialogFragment仅单个实例入栈
问题根因
现有方案失效的核心原因有两个:
DialogFragment.show()内部调用的是异步的FragmentTransaction.commit(),事务只会被提交到主线程消息队列等待执行,不会立刻完成Fragment的添加操作。7ms的间隔远小于主线程事务处理的耗时,前一次提交的事务还没执行时,findFragmentByTag查询不到还未添加的Fragment实例,多次判断都会通过,最终添加多个重复实例。synchronized同步块完全无效,因为flowWithLifecycle默认在主线程分发事件,所有showMyFragmentDialog的调用都运行在主线程,单线程环境下同步锁不会产生任何防护效果。
可行解决方案
方案1:Flow事件源层面过滤(最推荐)
从事件发送端处理重复触发问题,无需修改弹窗逻辑,适配性最高:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewModel.someSharedFlow .flowWithLifecycle(viewLifecycleOwner.lifecycle) // 相同内容的事件直接去重,可根据业务调整判断逻辑 .distinctUntilChanged() // 增加50ms防抖,覆盖7ms的最短发送间隔,只响应窗口期内最后一次事件 .debounce(50) .onEach { showMyFragmentDialog() } .launchIn(viewLifecycleOwner.lifecycleScope) }
如果你的SharedFlow用于发送一次性事件,建议设置extraBufferCapacity = 1 + onBufferOverflow = BufferOverflow.DROP_OLDEST,避免积压多余的弹窗事件。
方案2:新增原子标记位辅助判断
新增独立标记位管控弹窗状态,比单纯依赖FragmentManager查询更可靠:
// 原子布尔标记,兼容多线程事件分发场景 private val isDialogPendingShow = AtomicBoolean(false) private fun showMyFragmentDialog() { // 已有弹窗待显示直接返回 if (!isDialogPendingShow.compareAndSet(false, true)) return // 二次校验Fragment栈 if (childFragmentManager.findFragmentByTag(MyFragment.TAG) != null) { isDialogPendingShow.set(false) return } val dialog = MyFragment.newInstance(fuelTypes) // 弹窗关闭时重置标记位 dialog.setOnDismissListener { isDialogPendingShow.set(false) } dialog.show(childFragmentManager, MyFragment.TAG) }
需要注意在DialogFragment的onCancel、onDismiss生命周期回调中都处理标记位重置逻辑。
方案3:同步执行事务保证查询一致性
如果需要严格响应每一次事件,不使用防抖,可以用同步事务替代默认的异步提交,同时加状态判断避免崩溃:
private fun showMyFragmentDialog() { // 避免状态保存后提交事务崩溃 if (childFragmentManager.isStateSaved) return if (childFragmentManager.findFragmentByTag(MyFragment.TAG) != null) return val dialog = MyFragment.newInstance(fuelTypes) childFragmentManager.beginTransaction() .add(dialog, MyFragment.TAG) // 立刻执行事务,执行完成后FragmentManager即可查询到对应实例 .commitNow() }
这个方案和你之前尝试的showNow原理一致,新增isStateSaved判断即可解决原来的崩溃问题。
内容的提问来源于stack exchange,提问作者You Qi
相关产品推荐
相关产品推荐

