今天工作的时候在 bugly 上看到一个奔溃分析中有这么一个问题:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
于是就开始 Google,发现这问题由来已久,早在11年的时候就已经有人在提出这个问题。(时至今日仍出现这问题,不得不说 Google 你是可以的)
在 Stack Overflow 上的这篇文章中也说了一些奇技淫巧去解决问题,但是回答者也并不理解为什么会这样,只知道这样是可以解决的。而问题出现的原因其实也可以猜测出来,就是在 activity 多次的进行前后台切换后,导致了 fragment 的 commit 操作发生在 activity 的 onSaveInstanceState 之后。
本着探索一下的精神,开始看源码。首先看了 commit 的源码。戳开 commit 后,看到了 FragmentTransaction 这个抽象类。在源码的最后可以看到,这里定义了四个跟 commit 有关的方法。(由于英文比较渣,单看英文的注释似乎有点难以理解四个方法之间的差异,于是又去 Google 了一下,然后发现了这篇文章,有点茅塞顿开的感觉)
ok,接着继续来看 commit 的源码。
在BackStackRecord
这个类里看到了具体实现如下:
@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(" ", null, pw, null);
pw.close();
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
从上面可以发现,关键在于enqueueAction
这个方法上,于是继续跟,在FragmentManager
的源码发现如下:
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);
}
}
/**
* Adds an action to the queue of pending actions.
*
* @param action the action to add
* @param allowStateLoss whether to allow loss of state information
* @throws IllegalStateException if the activity has been destroyed
*/
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();
}
}
从上面可以看出,有三种情况会导致异常:
- Can not perform this action after onSaveInstanceState
- Can not perform this action inside of xxx
- Activity has been destroyed
针对方法:
- 使用
commitAllowingStateLoss
方法 - 避免在异步回调里进行 fragment transaction 的操作(可以参考这里最后几点)
- 把 commit 操作包含在
!isFinishing()
内部(这里给出了解决方案)
所以,个人比较推荐的 commit 姿势应该是:
if (!isFinishing()) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, fragment)
.commitAllowingStateLoss();
}
(这里说明一下,commitAllowingStateLoss
方法不是一定要这样的,就像在参考文章里说的,当你确定你无法避免那个异常的时候才用这个,官方推荐的是用 commit
;而使用commitAllowingStateLoss
导致的后果在参考文章里也有说明)
以上,纯粹是个人的小总结,也欢迎朋友们对文中不对的地方进行指正。Peace~~
(最后再唠嗑一句,英语过得去并且有耐心的朋友强烈推荐看一下参考文献的第一篇,讲得比较详细)
参考文献