在使用 Fragment 的时候偶尔会有这么一个报错, Can not perform this action after onSaveInstanceState
,意思为无法在 onSaveInstanceState
之后执行该操作,这个操作就是指 commit() ,以下是对这个问题的解析及对应解决办法的对比。
Fragment 是我们经常用到的东西,常用的操作有添加(add)、移除(remove)、替换(replace)等,这些操作组成一个集合 transaction ,我们在通过调用getSupportFragmentManager().beginTransaction() 来获取这个 FragmentTransaction 类的实例来管理这些操作,将他们存进由 activity 管理的backstack 中,这样我们就可以进行 fragment 变化的回退操作,也可以这样去获取 FragmentTransaction 类的实例。
FragmentManager mFragmentManager = getSupportFragmentManager();
FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();
为什么我们会有这种报错呢,因为我们在使用add(),remove(),replace() 等方法将 Fragment 的变化添加进去,然后在通过 commit 去提交这些变化(另外,在 commit 之前可以去调用 addToBackState() 方法,将这些变化加入到 activity 管理的 backstack 中去,这样用户调用返回键就可以回退这些变化了),提交完成之后这些变化就会应用到我们的 Fragment 中去。但是,这个 commit() 方法,你只能在 avtivity 存储他的状态之前调用,也就是 onSaveInstanceState() ,我们都知道 activity 有一个保存状态的方法和恢复状态的方法,这个就不详细解释了,在 onSaveInstanceState() 方法之后去调用 commit() ,就会抛出我们遇到的这个异常,这是因为在 onSaveInstanceState() 之后调用 commit() 方法,这些变化就不会被activity存储,即这些状态会被丢失,但我们可以去用 commitAllowingStateLoss() 这个方法去代替 commit() 来解决这个问题。
下面通过源码去看这两个方法的区别。
首先从我们获取 FragmentTransaction 类的实例开始,即getSupportFragmentManager() ,源码:
/**
* Return the FragmentManager for interacting with fragments associated
* with this activity.
*/
public FragmentManager getSupportFragmentManager() {
return mFragments;
}
而这个返回的 mFragments 是一个 FragmentManagerImpl 类 的实例,他继承自FragmentManager 这个类:
final FragmentManagerImpl mFragments = new FragmentManagerImpl();
我们在 FragmentManager 这个类中还看到 beginTransaction() 这个抽象方法,打开他的实现类
final class FragmentManagerImpl extends FragmentManager {
... ...
@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
.... ...
}
我们看到这个实现类中的该方法是返回一个 BackStateRecord 类的实体,我们继续去追踪这个类,就会发现,这个类其实是继承自 FragmentTransaction 的,并且,我们在这里看到我们熟悉的方法:
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
public BackStackRecord(FragmentManagerImpl manager) {
mManager = manager;
}
public int commit() {
return commitInternal(false);
}
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);
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
}
终于找到了我们有用的东西了,这里省略了其他不必要的代码,只留下我们需要用的核心代码,这里还有省略掉我们常用的add、remove、replace 等方法,回归正题,看我们要找的两个方法 commit() 和 commitAllowingStateLoss() ,他们都调用了 commitInternal(boolean allowStateLoss) 这个方法,只是传入的参数不同,我们去看 commitInternal 方法,该方法首先去判断是否已经 commit ,这个简单,直接跳过,最后他执行的是 FragmentTransactionImpl 类的 enqueueAction 方法,我们继续去追踪这个方法:
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mActivity == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mActivity.mHandler.removeCallbacks(mExecCommit);
mActivity.mHandler.post(mExecCommit);
}
}
}
通过这个方法我们可以看到,他首先去根据 commit 和 commitAllowingStateLoss 这两个方法传入的参数不同去判断,然后将任务扔进 activity 的线程队列中,这里我们重点去看的是 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);
}
}
这个方法很简单,就只是一个简单的判断而已,并且只有调用 commit 方法才会执行这里,commitAllowingStateLoss 则直接跳过这步,这里我们调用 commit 方法时,系统系判断状态(mStateSaved)是否已经保存,如果已经保存,则抛出"Can not perform this action after onSaveInstanceState"
异常,这就是我们遇到的问题了,而用 commitAllowingStateLoss 方法则不会这样,这就与我们之前分析的activity去保存状态对应上了,在 activity 保存状态完成之后调用 commit 时,这个 mStateSaved 就是已经保存状态,所以会抛出异常。