Problems encountered
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)
Copy the code
Two days ago, when I was using EventBus to operate on the Fragment in MainAcitvity on another page, I found this exception. The solution found online is to replace the Commit () method of the FragmentTransaction call with commitAllowingStateLoss(). As for why this method is used, I recently studied the source code and shared it here.
Source code analysis and validation
Public Abstract class FragmentTransaction {...... public abstract int commit(); public abstract int commitAllowingStateLoss(); }Copy the code
Start by looking at FragmentTransaction, a simple abstract class, and let’s look at the actual implementation class
Final class BackStackRecord extends FragmentTransaction implements FragmentManager BackStackEntry, Runnable {... public intcommit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true); } int commitInternal(Boolean allowStateLoss) {... mManager.enqueueAction(this, allowStateLoss); ... }... }Copy the code
Omit the irrelevant logic, commit() and commitAllowingStateLoss() both call the same method commitInternal(), The commitInternal() method receives a Boolean parameter allowStateLoss (allowStateLoss). The commitInternal() method calls the FragmentManager enqueueAction() method, And pass in the Boolean argument, and continue looking at FragmentManager:
Public Abstract class FragmentManager {...... boolean mStateSaved; ... public void enqueueAction(Runnable action, boolean allowStateLoss) {if(! allowStateLoss) { checkStateLoss(); } } private voidcheckStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState"); }}... ParcelablesaveAllState() {...if (HONEYCOMB) {
mStateSaved = true;
}
……
}
……
public void noteStateNotSaved() {
mStateSaved = false;
}
public void dispatchCreate() {
mStateSaved = false; ... } public voiddispatchActivityCreated() {
mStateSaved = false; ... } public voiddispatchStart() {
mStateSaved = false; ... } public voiddispatchResume() {
mStateSaved = false; ... }... public voiddispatchStop() {
mStateSaved = true; ... }... }Copy the code
EnqueueAction () makes a judgment based on the allowStateLoss argument. If allowStateLoss=false, that is, if you call commit(), enqueueAction() makes a judgment based on the allowStateLoss argument. If mStateSaved is true, then an exception will be thrown, which is the exception mentioned at the beginning of this article.
The crux of the question is how this mStateSaved has changed: As you can see from the above code, mStateSaved will be true in the saveAllState() and dispatchStop() methods. These methods are called in the FragmentActivity onSaveInstanceState() and onStop(), respectively. OnSaveInstanceState () is called after onPause() and before onStop(). If the Activity’s FragmentTransaction commit method is called after the Activity’s onSaveInstanceState() method is called, it will throw an exception (verify the conclusion at the beginning of this article).
In contrast, the other methods that change mStateSaved to false, based on the method name, can be inferred to run within the other callback methods in the FragmentActivity lifecycle, and it can be inferred that when the Activity returns to the top of the stack, there is no problem calling COMMIT.
conclusion
When a Fragment in an Activity changes, the FragmentManager will save the state of all the fragments at a specific point in time. This makes it easier for the Activity to reset the Fragment when it is rebuilt after being reclaimed. If the Fragment state is not saved, The Activity can only display each Fragment in the default way, which may not be the same as the app expects. If you have specific requirements in your project, such as controlling MainActivity fragments via broadcast or Eventbus from another Activity, then you need to use the commitAllowingStateLoss() method to avoid exceptions. You need to choose the right method to use according to your own needs.
If there are any mistakes or imprecise places in the article, I hope to mention them and study together.