java.lang.IllegalStateException Can not perform this action after onSaveInstanceState 解决方案

java.lang.IllegalStateException Can not perform this action after onSaveInstanceState 解决方案

Fragment

闪退复现

Handler().postDelayed({
    //打开页面后在 5 秒内按 home 或 back
     val fragment = Fragment()
     supportFragmentManager.beginTransaction()
     .replace(R.id.container, fragment)
     .commit() //此处会抛异常
}, 5_000)

错误日志

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
        at androidx.fragment.app.FragmentManagerImpl.checkStateLoss(FragmentManagerImpl.java:1536)
        at androidx.fragment.app.FragmentManagerImpl.enqueueAction(FragmentManagerImpl.java:1558)
        at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:317)
        at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:282)
        at com.louis.mykttest.MainActivity.onCreate$lambda-0(MainActivity.kt:47)

原因分析

//commit 抽象方法
FragmentTransaction#commit
//commit 抽象方法实现
- BackStackRecord#commit
-- BackStackRecord#commitInternal(allowStateLoss = false)
--- FragmentManagerImpl#enqueueAction(allowStateLoss = false)
---- FragmentManagerImpl#checkStateLoss
----- FragmentManagerImpl#isStateSaved
isStateSaved 方法返回 true 则会直接抛出该 IllegalStateException 异常

解决方案

思路1 提前判断 isStateSaved 状态
//提前判断
//注意这里是 FragmentManager#isStateSaved 不是 Fragment#isStateSaved,因为此时 FragmentManager 和 Fragment 还未关联起来,所以 Fragment#mFragmentManager 为 null
if (supportFragmentManager.isStateSaved) {
    return
}
val fragment = Fragment()
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.commit()
思路2 改用 commitAllowingStateLoss 方法
 val fragment = Fragment()
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.commitAllowingStateLoss()

DialogFragment

闪退复现

Handler(mainLooper).postDelayed({
   	//打开页面后在 5 秒内按 home 或 back
   	val dialogFragment = DialogFragment()
  	dialogFragment.show(supportFragmentManager, "")
}, 5_000)

原因分析

DialogFragment#show
//commit 抽象方法
- FragmentTransaction#commit
//commit 抽象方法实现
-- BackStackRecord#commit
--- BackStackRecord#commitInternal(allowStateLoss=false);
---- FragmentManagerImpl#enqueueAction(allowStateLoss=false);
----- FragmentManagerImpl#checkStateLoss
------ FragmentManagerImpl#isStateSaved
isStateSaved 方法返回 true 则会直接抛出该 IllegalStateException 异常

解决方案

思路1 提前判断 isStateSaved 状态

重写 DialogFragment#show 方法

override fun show(manager: FragmentManager, tag: String?) {
//提前判断
//注意这里是 FragmentManager#isStateSaved 不是 Fragment#isStateSaved,因为此时 FragmentManager 和 Fragment 还未关联起来,所以 Fragment#mFragmentManager 为 null
    if (manager.isStateSaved) {
       Log.e(TAG, "show: 原本要抛异常")
       return
    }
    super.show(manager, tag)
}
思路2 增加 showAllowingStateLoss 方法
因为有 
DialogFragment#dismiss -> 直接调用 DialogFragment#dismissInternal(allowStateLoss = false)
//
DialogFragment#dismissAllowingStateLoss -> 直接调用 DialogFragment#dismissInternal(allowStateLoss = true)
//
DialogFragment#show -> 间接调用 BackStackRecord#commit -> 直接调用BackStackRecord#commitInternal(allowStateLoss=false)
//
但为啥没有 DialogFragment#showAllowingStateLoss 方法???
使得
DialogFragment#showAllowingStateLoss -> 间接调用 BackStackRecord#commitAllowingStateLoss -> 直接调用 BackStackRecord#commitInternal(allowStateLoss = true)
所以考虑新增一个方法实现该逻辑

重写 DialogFragment,反射处理 mDismissed 和 mShownByMe 状态

fun showAllowingStateLoss(manager: FragmentManager, tag: String?) {
        /**
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit();
         */
        /*
          mDismissed = false
          mShownByMe = true
          */
       DialogFragmentHelper.setFieldValue(this, "mDismissed", false)
       DialogFragmentHelper.setFieldValue(this, "mShownByMe", true)
       val ft = manager.beginTransaction()
       ft.add(this, tag)
       ft.commitAllowingStateLoss()
}

附:DialogFragmentHelper

object DialogFragmentHelper {
    fun setFieldValue(obj: Any, fieldName: String, fieldValue: Any) {
        //androidx.fragment.app.DialogFragment.mDismissed
        //androidx.fragment.app.DialogFragment.mShownByMe
        //java.lang.NoSuchFieldException: No field mDismissed in class Lcom/louis/mykttest/FixedDialogFragment; (declaration of 'com.louis.mykttest.FixedDialogFragment' appears in
//        val field = obj.javaClass.getDeclaredField(fieldName)
        val field = DialogFragment::class.java.getDeclaredField(fieldName)
        field.isAccessible = true
        field[obj] = fieldValue
    }
    fun <T> getFieldValue(obj: Any, fieldName: String): T {
        val field = DialogFragment::class.java.getDeclaredField(fieldName)
        field.isAccessible = true
        return field[obj] as T
    }
}

利用扩展函数简化

  • 可以采用扩展函数替代重写操作
//不要用 show 这个名字
fun DialogFragment.showFixed(manager: FragmentManager, tag: String?) {
    //
    if (manager.isStateSaved) {
        Log.e(TAG, "showFixed 原本要抛异常")
        return
    }
    this.show(manager, tag)
}
//
fun DialogFragment.showAllowingStateLoss(manager: FragmentManager, tag: String) {
    /**
    mDismissed = false;
    mShownByMe = true;
    FragmentTransaction ft = manager.beginTransaction();
    ft.add(this, tag);
    ft.commit();
     */
	/*
      mDismissed = false
      mShownByMe = true
     */
    DialogFragmentHelper.setFieldValue(this, "mDismissed", false)
    DialogFragmentHelper.setFieldValue(this, "mShownByMe", true)
    val ft = manager.beginTransaction()
    ft.add(this, tag)
    ft.commitAllowingStateLoss()
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

louisgeek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值