从源码角度理解Can not perform this action after onSaveInstanceState异常

本文深入探讨了在Android开发中常见的“Cannot perform this action after onSaveInstanceState”异常,详细解析了异常产生的原因,尤其是在FragmentTransaction的commit()和Activity的onBackPressed()时。文章提供了多种解决方案,包括使用commitAllowingStateLoss()替代commit(),以及通过设置标志位来避免状态保存后的操作冲突。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在开发中经常遇到Fatal Exception: java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState异常,那这个异常出现原因是什么呢,怎么解决呢?

问题描述

出现Fatal Exception: java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState异常有两种情况:
1.FragmentTransaction的commit()时出现:
具体堆栈信息如下:
在这里插入图片描述
2.Activity/FragmentActivity的onBackPressed时出现:
具体堆栈信息如下:
crash具体堆栈信息

问题原因和解决方法

出现Fatal Exception: java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState异常细分为两种情况,但产生原因是一样的,都是因为在存储状态之后调用了commit()/onBackPressed()方法,在commit()/onBackPrssed()中会调用checkStateLoss()方法,具体如下:

private void checkStateLoss() {
    if (this.mStateSaved) {
      throw new IllegalStateException("Can not perform this action after onSaveInstanceState");
    } else if (this.mNoTransactionsBecause != null) {
      throw new IllegalStateException("Can not perform this action inside of " + this.mNoTransactionsBecause);
    }
  }

其中如果this.mStateSaved为true,就会抛出这个异常。而对于mStateSaved用了保存Fragment状态,是在Activity#onSaveInstanceState时通过调用FragmentManager#saveAllState方法,来进行Fragment的状态保存,同时设置mStateSaved为true,用来标识状态已被保存过。下面具体分析两种情况:
1.FragmentTransaction的commit()时出现:
FragmentTransaction的commit()会调用BackStackRecord.java的commit()方法,可以看到commit()和commitAllowingStateLoss()的具体实现,如下:
BackStackRecord.java

@Override
    public int commit() {
        return commitInternal(false);
    }

    @Override
    public int commitAllowingStateLoss() {
        return commitInternal(true);
    }
    int commitInternal(boolean allowStateLoss) {
        if (mCommitted) throw new IllegalStateException("commit already called");
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Commit: " + this);
            LogWriter logw = new LogWriter(TAG);
            PrintWriter pw = new PrintWriter(logw);
            dump("  ", null, pw, null);
            pw.close();
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

可以看出commit()和commitAllowingStateLoss()都是调用commitInternal()方法,只是传参不一样,而在commitInternal()方法中,根据传参会调用FragmentManager的enqueueAction方法,具体如下:

public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        synchronized (this) {
            if (mDestroyed || mHost == null) {
                throw new IllegalStateException("Activity has been destroyed");
            }
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<>();
            }
            mPendingActions.add(action);
            scheduleCommit();
        }
    }

如果allowStateLoss为true的话会调用checkStateLoss()方法进行检测。所以从源码可知commit()和commitAllowingStateLoss()的区别在于,commit()方法会检测fragment的状态,而commitAllowingStateLoss()不会对状态进行检测,状态会丢失。
由此得到结论和解决方法:
(1)在activity的生命周期方法中提交事务要小心,越早越好,比如onCreate或是在接收用户的输入时来提交。尽量避免在onActivityResult()方法中提交。
(2)避免在异步的回调方法中执行commit。因为他们感知不到当前activity生命周期的状态。
(3)如果ui状态的改变对用户来说是可以接受的话使用commitAllowingStateLoss()代替commit()。
2.Activity/FragmentActivity的onBackPressed时出现:
按返回键时会调用super.onBackPressed()方法,如下:
FragmentActivity.java中

@Override
    public void onBackPressed() {
        if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
            super.onBackPressed();
        }
    }

会调用FragmentManagerImpl的popBackStackImmediate()方法,如下:

@Override
    public boolean popBackStackImmediate() {
        checkStateLoss();
        return popBackStackImmediate(null, -1, 0);
    }

会调用checkStateLoss()方法,同上,会检测状态。
那这个问题怎么解决呢?
(1)添加try…catch,感觉这种方法并没有解决根本问题,不推荐。
(2)重写onSaveInstanceState方法,不调用super,但onSaveInstanceState方法是用来存储状态使用的,不调用super,防止系统保存fragment的状态,可能会引发一引起其他的问题;再有就是,对于support包下,在其onStop时也会把mStateSaved置为true,仍然能够遇到state loss exception。不推荐。
(3)设置标志位,状态保存过后,不处理KEY事件。具体如下:

public class FragmentStateLossActivity extends Activity {
    private static final String TAG = "Fragment state loss";
    private boolean mStateSaved;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment_state_loss);
        mStateSaved = false;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        // Not call super won't help us, still get crash
        super.onSaveInstanceState(outState);
        imStateSaved = true;
    }

    @Override
    protected void onResume() {
        super.onResume();
        mStateSaved = false;
    }

    @Override
    protected void onStop() {
        super.onStop();
        mStateSaved = true;
    }

    @Override
    protected void onStart() {
        super.onStart();
        mStateSaved = false;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (!mStateSaved) {
            return super.onKeyDown(keyCode, event);
        } else {
            // State already saved, so ignore the event
            return true;
        }
    }

    @Override
    public void onBackPressed() {
        if (!mStateSaved) {
            super.onBackPressed();
        }
    }
}

但这种方法虽然能从根本上解决crash,但相对比较麻烦。
目前还没有发现更好的方法。

参考

https://juejin.im/entry/58636864128fe10069efc4b5
https://huxian99.github.io/2016/08/28/cj3qymo360000owxk9zp17alo/

### 回答1: 这个错误是因为在 onSaveInstanceState() 方法之后尝试执行某些操作,这是不允许的。 onSaveInstanceState() 方法是在 Activity 即将被销毁之前调用的,它的主要作用是保存 Activity 的状态信息,以便在 Activity 重新创建时恢复状态。因此,在 onSaveInstanceState() 方法之后执行某些操作可能会导致状态信息丢失或不一致。如果需要在 onSaveInstanceState() 方法之后执行某些操作,可以考虑将这些操作放在 onResume() 方法中执行。 ### 回答2: 在Android开发中,当我们的应用程序遇到设备旋转或配置更改等情况时,系统可能会销毁并重建活动,以适应新的设备状态。在这种情况下,在 onSaveInstanceState() 方法中,我们通常将一些重要的应用程序状态保存到 Bundle 对象中,以便在活动重新创建时进行恢复。但是,在 onSaveInstanceState() 方法调用后,一些操作可能会受到限制,因为活动实际上已被销毁。其中包括使用 FragmentTransaction 进行 Fragment 操作,或者提交 AsynTask 等异步操作。如果在此时尝试执行此类操作,则会导致崩溃或其他不可预测的结果。 如果您需要在 onSaveInstanceState() 方法之后执行某些操作,您可以使用简单的技巧来避免出现错误。一种方法是使用 Handler 来执行延迟操作。使用此方法,您可以先在 onSaveInstanceState() 方法中将 Runnable 对象传递给 Handler,并设置合适的延迟时间。当 Runnable 对象在指定时间后在主线程上执行时,Activity 已经重建,所以不存在任何问题。 另一个解决方案是使用 onSaveInstanceState() 方法的一种变体,即 onSaveInstanceState(Bundle, PersistableBundle) 方法。该方法与 onSaveInstanceState(Bundle) 方法不同的是,它还接收一个 PersistableBundle 参数,该参数可以在拥有足够空间的设备上持久化保存。这样,即使活动被销毁并重建,也可以恢复 PersistableBundle 中保存的状态。 总之,在 onSaveInstanceState() 方法被调用后,您应避免执行可能影响 Activity 生命周期的任何操作。如果确实需要执行此类操作,请使用上述技巧来确保正确性和稳定性。 ### 回答3: onSaveInstanceState是Android中一个很重要的生命周期方法之一,它通常在Activity或Fragment即将被销毁之前被调用,用于保存Activity或Fragment的状态,以便在Activity或Fragment被重建时恢复它们的状态。在这个方法被调用后,Activity或Fragment的状态已经被保存,如果继续进行操作,可能会导致状态丢失或不一致。 因此,如果在onSaveInstanceState方法被调用后再尝试执行某些操作,就会出现 can not perform this action after onSaveInstanceState 的错误信息。 例如,在Activity的onSaveInstanceState方法被调用后,如果尝试在onPause方法中执行一个Fragment的事务,就会出现这个错误。这是因为在onSaveInstanceState方法被调用后,Fragment的状态已经被保存,并且任何与Fragment相关的操作都不应该被执行,以避免状态丢失或不一致。 为了避免出现can not perform this action after onSaveInstanceState的错误,可以在onSaveInstanceState方法被调用后,避免执行任何与状态相关的操作。如果确实需要在onSaveInstanceState之后执行某些操作,可以考虑使用Handler或post方法进行延迟执行,以确保状态已经被保存并恢复,而不会出现错误。另外,可以尝试使用Fragment的setRetainInstance方法保留Fragment的实例,以避免重建时出现状态丢失的问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值