遇到的问题
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2044)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2067)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:680)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:634)
前两天使用EventBus在别的页面对MainAcitvity里的Fragment进行操作时,爆出来这个异常,网上找到的解决方案是把 FragmentTransaction 调用的commit()方法替换为 commitAllowingStateLoss()方法就可以解决了,至于为什么用这个方法,最近研究了一下源码,在这里分享一下。
源码分析及验证
public abstract class FragmentTransaction {
……
public abstract int commit();
public abstract int commitAllowingStateLoss();
}
首先查看FragmentTransaction,很简单的一个抽象类,不多说,下面寻找真正的实现类
在这里插入图片描述
通过上图的方式,右键类名点击 Find Usages,然后在Find视图窗口里找到继承自FragmentTransaction的实现类:BackStackRecord,下面来看一下这个类:
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
……
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
int commitInternal(boolean allowStateLoss) {
……
mManager.enqueueAction(this, allowStateLoss);
……
}
……
}
省略掉无关逻辑,commit()和commitAllowingStateLoss()两个方法都调用了同一个方法commitInternal(),commitInternal()方法接收一个boolean的参数allowStateLoss(允许状态丢失),commitInternal()方法里调用了FragmentManager的enqueueAction()方法,并且把boolean参数传递了进去,然后继续看FragmentManager:
public abstract class FragmentManager {
……
boolean mStateSaved;
……
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
}
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
}
……
Parcelable saveAllState() {
……
if (HONEYCOMB) {
mStateSaved = true;
}
……
}
……
public void noteStateNotSaved() {
mStateSaved = false;
}
public void dispatchCreate() {
mStateSaved = false;
……
}
public void dispatchActivityCreated() {
mStateSaved = false;
……
}
public void dispatchStart() {
mStateSaved = false;
……
}
public void dispatchResume() {
mStateSaved = false;
……
}
……
public void dispatchStop() {
mStateSaved = true;
……
}
……
}
这里可以看出来,enqueueAction()根据allowStateLoss参数做了一个判断,如果allowStateLoss=false,也就是如果你调用的是commit()方法的话,那么就要做一个判断,如果mStateSaved为true,那么就要抛出异常,这个异常就是文章开头所说的那个异常。
问题的关键来了,这个mStateSaved是如何变化的:
上面的代码可以看出在saveAllState()方法和dispatchStop() 方法中,mStateSaved会变为true(HONEYCOMB意思是是否大于api11,不用管),这两个方法分别是在FragmentActivity的onSaveInstanceState()和onStop()中被调用,而onSaveInstanceState()的调用时机是在onPause()之后onStop()之前,这样可以总结出来当Activity的onSaveInstanceState()方法调用之后如果调用了该Activity的FragmentTransaction的commit方法,就会抛出异常(验证文章开头的结论)。
相反的,其他几个将mStateSaved变为false的方法,根据方法名可以推断出是在FragmentActivity的生命周期的其他几个回调方法里运行的,可以推断出,当Activity重新回到栈顶显示之后,调用commit是没有问题的。
总结
当Activity中的Fragment发生了变化,FragmentManager会在特定的时间点保存所有Fragment的状态,方便Activity因为被回收之后重建时,重新设置Fragment,如果状态没有被保存,那么Activity就只能按照默认方式显示每个Fragment,显示效果可能跟app的预期不一样。
如果项目中有特定的需求,比如需要在别的Activity中通过广播或者Eventbus的方式控制MainActivity的Fragment切换,那么就需要使用commitAllowingStateLoss()方法来避免异常。
大家需要按照自己的需求来选择合适的方法使用。
如果文章中有错误或者不严谨的地方,希望提出来,共同学习。
网友评论