Fragment 事务提交异常 Can not perform this action after onSaveInstanceState

最近查看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这些状态值,只不过没有暴露给子类,尬- -。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JavPer

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

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

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

打赏作者

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

抵扣说明:

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

余额充值