Fragment中出现Can not perform this action after onSaveInstanceState

前言

在项目开发中,将app安装在某些手机上时,有时会出现闪退的情况。通过定位抛出的异常信息如下:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1522)
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1540)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:654)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:621)
.....

分析上面的异常信息,得知原来是使用Fragment时,FragmentTransaction调用commit()方法时,抛出的异常。

分析异常

通过字面意思理解,不能在onSaveInstanceState之后,执行这个操作,这里这个操作是指commit()。如果你在onSaveInstanceState()方法调用之后,执行commit()方法,这个FragmentTransaction将不会被记住,从用户的角度来看,这个FragmentTransaction会丢失,可能导致UI状态丢失。为了保证用户的体验,Android为了避免用户的状态丢失,这时就会抛出一个IllegalStateException异常。

源码分析异常产生的过程
我们经常对Fragment进行的操作有,add,remove,replace等,而这些操作都是通过FragmentTransaction来进行管理的。我们可以通过这样获得实例:

FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();

我们首先通过获取FragmentManager开始,查看代码的执行过程。
首先看getSupportFragmentManager()方法的内部:

    public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
    }

这个方法还是内部执行getSupportFragmentManager,进入之后:

    FragmentManagerImpl getFragmentManagerImpl() {
        return mFragmentManager;
    }

这里看到返回了mFragmentManager ,而这个mFragmentManager 是一个FragmentManagerImpl 实例,它是FragmentManager的子类:

final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();

继续从这个FragmentManagerImpl 类中,我们查看到beginTransaction方法。

final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
    ....
    @Override
    public FragmentTransaction beginTransaction() {
        return new BackStackRecord(this);
    }
    ...
}

这里返回的是一个BackStackRecord实例对象,继续追踪BackStackRecord类,我们可以发现它是继承FragmentTransaction类的对象,于是我们可以知道针对Fragment的操作,都是在这个类中进行。
在这个类中,我们看到了熟悉的方法,和与之相似的另一个方法commitAllowingStateLoss,它们都执行了相同的方法,只是传入的参数不同而已:

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

    public int commitAllowingStateLoss() {
        return commitInternal(true);
    }

所以当我们执行FragmentTransaction的commit()方法时,系统内部调用的是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("  ", null, pw, null);
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

这个方法中,重要的一步是执行enqueueAction(this, allowStateLoss),继续进入enqueueAction方法中:

    public void enqueueAction(Runnable 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<Runnable>();
            }
            mPendingActions.add(action);
            if (mPendingActions.size() == 1) {
                mHost.getHandler().removeCallbacks(mExecCommit);
                mHost.getHandler().post(mExecCommit);
            }
        }
    }

由于我们前面进入方法时,传入的allowStateLoss为false,所以这里会执行checkStateLoss()方法,从这个方法中,终于找到了报错的信息:

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

在这里当mStateSaved为true时,方法内部就会抛出异常信息。所以我们需要分析mStateSaved在什么情况下为ture,继续追踪mStateSaved属性,我们看到方法saveAllState中设置了它的属性为true:

    Parcelable saveAllState() {
...
        if (HONEYCOMB) {
            // As of Honeycomb, we save state after pausing.  Prior to that
            // it is before pausing.  With fragments this is an issue, since
            // there are many things you may do after pausing but before
            // stopping that change the fragment state.  For those older
            // devices, we will not at this point say that we have saved
            // the state, so we will allow them to continue doing fragment
            // transactions.  This retains the same semantics as Honeycomb,
            // though you do have the risk of losing the very most recent state
            // if the process is killed...  we'll live with that.
            mStateSaved = true;
        }
...
    }

其中属性HONEYCOMB已经定义:

static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11;

也就是说当系统版本号大于等于11时,当执行了saveAllState的方法时,mStateSaved 就会为true。而这个saveAllState()方法是在系统保存状态后执行的。
到这里我们梳理了FragmentTransaction执行commit时,系统内部的执行过程。也清楚了为什么会出现Can not perform this action after onSaveInstanceState问题。当系统内部调用onSaveInstanceState()后,这时如果进行commit的操作,就会抛出异常。如果想要避免这个异常,我们可以选择执行commitAllowingStateLoss()方法,这样就能忽略状态丢失的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值