错误信息:
Fatal Exception: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at androidx.fragment.app.FragmentManager.checkStateLoss(FragmentManager.java:1844)
at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1884)
at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:329)
at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:294)
at androidx.fragment.app.DialogFragment.show(DialogFragment.java:260)
背景:
在请求某业务Api时,网络请求失败,弹出DialogFragment,比如示例
val builder = CommonConfirmDialog.Builder() val tipDialog = builder.setType(CommonConfirmDialog.TYPE_NO_CANCEL).setCancelOutSide(true) .setShowTitle(false).setBoldContent(true).setContent(errorMsg).build() tipDialog.show(supportFragmentManager, "xxx_tip")
结果在 show的时候发生crash Fatal Exception: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState;
原因分析:
出现 IllegalStateException 是因为在 onSaveInstanceState 之后尝试执行 Fragment 事务。要解决这个问题,可以采用以下方法之一:出现 IllegalStateException 是因为在 onSaveInstanceState 之后尝试执行 Fragment 事务(如添加、移除或替换Fragment),这会导致状态丢失问题。
解决建议:
- 使用
commitAllowingStateLoss()
这会允许状态丢失,但要谨慎使用,因为可能会导致意外问题。
-
在
onSaveInstanceState之前执行事务: 确保在onSaveInstanceState方法调用之前完成所有Fragment事务。 -
使用Handler或其他方式延迟事务: 使用Handler将事务推迟到
onSaveInstanceState之后。
使用 commitAllowingStateLoss() 可能会导致一些意外问题:
- 状态丢失:在某些情况下,如果应用程序在提交事务后被系统终止,可能会丢失一些状态或数据。这可能会导致不一致的 UI 状态。
- 不一致的用户体验:由于状态可能丢失,用户可能会看到不一致的界面或操作,这可能会导致混淆或不良体验。
- 调试困难:由于状态丢失的问题在调试过程中可能不容易发现,因此使用
commitAllowingStateLoss()可能会使问题更难排查。
commitAllowingStateLoss() 应该作为最后的手段,只有在其他方法无法解决问题时才使用。
解决办法:
commitAllowingStateLoss()
val builder = CommonConfirmDialog.Builder() val tipDialog = builder.setType(CommonConfirmDialog.TYPE_NO_CANCEL) .setCancelOutSide(true) .setShowTitle(false) .setBoldContent(true) .setContent(errorMsg) .build() tipDialog.show(supportFragmentManager, "xxx_tip")
将 show 方法修改为以下形式:
supportFragmentManager.beginTransaction().add(tipDialog, "xxx_tip").commitAllowingStateLoss()
在 onSaveInstanceState 之前执行事务
确保事务在 onResume() 方法中完成,以确保它在 onSaveInstanceState() 之前执行,调用之前完成对话框的显示。例如,在 onResume 中执行:
override fun onResume() {
super.onResume()
val builder = CommonConfirmDialog.Builder()
val tipDialog = builder.setType(CommonConfirmDialog.TYPE_NO_CANCEL)
.setCancelOutSide(true)
.setShowTitle(false)
.setBoldContent(true)
.setContent(errorMsg)
.build()
tipDialog.show(supportFragmentManager, "xxx_tip")
}
使用 Handler 延迟事务
val builder = CommonConfirmDialog.Builder()
val tipDialog = builder.setType(CommonConfirmDialog.TYPE_NO_CANCEL)
.setCancelOutSide(true)
.setShowTitle(false)
.setBoldContent(true)
.setContent(errorMsg)
.build()
val transaction = supportFragmentManager
val fragment: Fragment? = transaction.findFragmentByTag("xxx_tip")
if (fragment != null) {
val fragmentTransaction: FragmentTransaction = transaction.beginTransaction() fragmentTransaction.remove(fragment)
fragmentTransaction.commit()
}
Handler(Looper.getMainLooper()).post {
tipDialog.show(supportFragmentManager, "xxx_tip")
}
commitNow()
commit()`: 事务是异步提交的,意味着它会被排队到主线程的消息队列中,可能会有一定的延迟。
commitNow()`: 事务是同步提交的,意味着它会立即应用,确保事务的操作在代码继续执行之前完成。
commitNow 的好处:
立即应用
使用 commitNow() 可以确保事务会立即生效。这在某些情况下很重要,比如当你需要在继续执行其他操作之前确保 Fragment 事务已经完成时。
避免 UI 问题
如果你在进行一些依赖于 Fragment 的 UI 操作(例如更改 Fragment 的 UI 状态),commitNow() 确保这些更改会立即可见,从而避免了因事务未完成而导致的 UI 问题。
减少潜在问题
在某些复杂的 Fragment 事务管理中,使用 commitNow() 可以帮助确保事务状态在继续执行其他代码之前是一致的,从而减少因事务未提交而导致的潜在问题。
总结:
根据实际情况选择合适的方法来确保Fragment事务在正确的生命周期内执行。