最近查看Bugly错误统计时,看到这样一个异常信息:java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState,代码定位:transaction.commit()这一行,现做个问题总结,希望能帮到遇到同问题的朋友。
查看下源码
@Override public int commit() { return commitInternal(false); } 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(" ", pw); pw.close(); } mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; } public void enqueueAction(OpGenerator action, boolean allowStateLoss) { if (!allowStateLoss) { checkStateLoss(); } synchronized (this) { if (mDestroyed || mHost == null) { if (allowStateLoss) { // This FragmentManager isn't attached, so drop the entire transaction. return; } throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { mPendingActions = new ArrayList<>(); } mPendingActions.add(action); scheduleCommit(); } } private void checkStateLoss() { if (isStateSaved()) { throw new IllegalStateException( "Can not perform this action after onSaveInstanceState"); } } @Override public boolean isStateSaved() { // See saveAllState() for the explanation of this. We do this for // all platform versions, to keep our behavior more consistent between // them. return mStateSaved || mStopped; }追踪 transaction.commit()方法,最终BackStackRecord 类调用FragmentManagerImpl的checkStateLoss(),判断mStateSaved或者mStopped为true,就会抛出这个异常信息,意思是说 在onSaveInstanceState之后或者onStop之后不能再执行此操作。
1、 transaction.commit()操作
/** * Schedules a commit of this transaction. The commit does * not happen immediately; it will be scheduled as work on the main thread * to be done the next time that thread is ready. * * <p class="note">A transaction can only be committed with this method * prior to its containing activity saving its state. If the commit is * attempted after that point, an exception will be thrown. This is * because the state after the commit can be lost if the activity needs to * be restored from its state. See {@link #commitAllowingStateLoss()} for * situations where it may be okay to lose the commit.</p> * * @return Returns the identifier of this transaction's back stack entry, * if {@link #addToBackStack(String)} had been called. Otherwise, returns * a negative number. */ public abstract int commit();源码说明大概意思:commit transaction;不会立刻执行,它会被调度为主线程上的工作,下次线程准备好时再执行;使用此方法提交事务,只能在其activity保存状态之前,如果提交事务在其activity保存状态之后就会引发异常,因为commit之后的状态会丢失,如果activity需要restored状态。使用commitAllowingStateLoss() 方法提交事务 状态丢失是可以的;返回此事务的堆栈项的标识符,如果调用了addToBackStack(String)。否则,返回一个负数。 那么如果不考虑状态的丢失,提交事务 改成commitAllowingStateLoss(),就不会有这个异常信息,但是如果activity恢复,那么commit 的将会丢失,这肯定不行。 这里可以看到commit是不会立刻执行的,但是源码里有commitNow()方法,这个方法是可以即刻执行的,但是这个方法不支持addToBackStack(String),看具体需求使用了,同样commitNow()也不可以在activity 保存状态之后使用,commitNowAllowingStateLoss()方法可以,但是它同样是危险的,会丢失commit。 一般情况下,transaction的add、show、hide等行为 commit时都是与用户交互后产生的,我这里的commit是存在异步网络请求之后,根据后端数据确定add某个Fragment或者show、hide某个Fragment,这里就有可能发生这个异常,理论上异步请求返回后执行commit时,此界面已经onSaveInstanceState或者onStop了,那就会抛出这个异常了。
综合以上分析,现开始着手解决: 1、确保transaction执行commit时,界面处于onSaveInstanceState和onStop 前。 2、onSaveInstanceState 后,保存界面关键参数。 2、假如界面会被重新创建恢复状态,先获取关键参数,再去执行commit操作。
我们知道onSaveInstanceState()方法是在当 手机内存不足,回收activity之前调用,一般是在onPause()之后,肯定是在onStop之前,我们可以在此时做下记录,比如:
var mCurrentPosition:Int = 0 var mUserID:String?=null var mCurrentFragmentType:Int = 0 var mAfterSavedState:Boolean = false var mStopp = false override fun onSaveInstanceState(outState: Bundle) { outState.putString("user_id",mUserID) outState.putInt("current_position",mCurrentPosition) outState.putInt("current_fragmentType",mCurrentFragmentType) super.onSaveInstanceState(outState) mAfterSavedState = true } override fun onStop() { super.onStop() mStopState = true }然后在transaction执行commit时去做判断,在activity恢复重新创建时获取关键值,比如:
// 异步请求回来后,先记录数据值,如果不满足条件,不提交事务 if(mAfterSavedState||mStopState) return // transaction 操作,如add,show //commit transaction.commit() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if(savedInstanceState!=null){ //activity 被回收后从新创建 mUserID = savedInstanceState.getString("user_id") mCurrentFragmentType = savedInstanceState.getInt("current_fragmentType") mCurrentPosition= savedInstanceState.getInt("current_position") }else{ mUserID = intent.getStringExtra("user_id") } // 请求网络,加载数据 // 请求网络,网络回来后还要判断数据,执行transaction } override fun onResume() { super.onResume() // 这里不要忘记重置标记 mAfterSavedState = false mStopState = false // 根据参数,执行stop前 未执行的transaction //transaction.commit() } override fun onPause() { super.onPause() //mCurrentPosition = }当然了 你也可以使用onRestoreInstanceState方法,如果activity被回收重新创建,此方法肯定会走,Bundle直接取值即可,这里需要注意,网络请求不要放在onCreate方法了,因为请求需要的参数onCreate时拿不到,放到onRestoreInstanceState之后的onResume方法中处理,根据具体需求决定是否需要每次Resume都加载数据:
override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) mUserID = savedInstanceState.getString("user_id") mCurrentFragmentType = savedInstanceState.getInt("current_fragmentType") mCurrentPosition= savedInstanceState.getInt("current_position") } override fun onResume() { super.onResume() // 这里不要忘记重置标记 mAfterSavedState = false mStopState = false if(mLoadData){ // 请求网络,网络回来 后还要判断数据,执行transaction } // 根据参数,执行stop前 未执行的transaction //transaction.commit() }到这里此异常的解决方案基本总结完毕,可以稍微封装一下,把状态值mAfterSavedState和mStopState 放到基类BaseActivity中,基类中直接记录这俩状态,子类直接使用;父类封装commit方法供子类使用,commit之前检查状态;具体界面的onSaveInstanceState各自去处理,查看FragmentActivity中,有mCreated,mResumed,mStopped这些状态值,只不过没有暴露给子类,尬- -。