前言
在项目开发中,将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()方法,这样就能忽略状态丢失的情况。