之前在使用Fragment的时候偶尔会有这么一个报错,Can not perform this action after onSaveInstanceState,意思为无法再onSaveInstanceState之后执行该操作,这个操作就是指commit(),之前也没怎么在意,后来通过查看源码去了解了一下这个问题,以下是对这个问题的解析及对应解决办法的对比。
Fragment是我们经常用到的东西,常用的操作有添加(add)、移除(remove)、替换(replace)等,这些操作组成一个集合transaction,我们在通过调用getSupportFragmentManager().beginTransaction()来获取这个FragmentTransaction类的实例来管理这些操作,将他们存进由activity管理的back stack中,这样我们就可以进行fragment变化的回退操作,也可以这样去获取FragmentTransaction类的实例:
FragmentManager mFragmentManager = getSupportFragmentManager();
FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();
为什么我们会有这种报错呢,因为我们在使用add(),remove(),replace()等方法将Fragment的变化添加进去,然后在通过commit去提交这些变化(另外,在commit之前可以去调用addToBackState()方法,将这些变化加入到activity管理的back stack中去,这样用户调用返回键就可以回退这些变化了),提交完成之后这些变化就会应用到我们的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就是已经保存状态,所以会抛出异常。
长篇大论终于讲完了,其实回头一看并没有多么复杂,就跟着源码一步一步去找,就会找到我们发生错误的地方,看了源码之后发现,原来并没有多么难,so easy!哈哈