Source code analysis of the difference between commitAllowingStateLoss() and commit().

Keywords: Fragment

There was an occasional error when using Fragment. Can't perform this action after onSave Instance State. It means that it can't be performed after onSave Instance State. This operation means commit(). It didn't pay much attention to it before. Later, I learned about this problem by looking at the source code. Here's an analysis of this problem and how to solve it. The comparison of methods.

Fragmentation is something we often use. Common operations include add, remove, replace, etc. These operations form a set transaction. We are managing these operations by calling getSupportFragmentManager().beginTransaction() to get instances of the FragmentTransaction class and storing them in the stack managed by activity. In this way, we can do a fallback operation for fragment ation changes, or we can get an instance of the FragmentTransaction class as follows:

  1. FragmentManager  mFragmentManager = getSupportFragmentManager();    
  2. FragmentTransaction  mFragmentTransaction = mFragmentManager.beginTransaction();    
Why do we have this kind of error, because we add Fragment changes using add(),remove(),replace() and other methods, and then submit these changes through commit (in addition, before commit, we can call addToBackState() method to add these changes to the back of activity management In stack, the user calls the return key to return these changes, which will be applied to our Fragment after the submission is completed. However, this commit() method can only be invoked before avtivity stores its state, that is, onSaveInstanceState(). We all know that activity has a method of saving state and a method of restoring state. This is not explained in detail. Calling commit() after onSaveInstanceState() method will throw the exception we encounter, because onSaveIn. StanceState () then calls the commit() method, and these changes will not be stored by activity, that is, these States will be lost, but we can use commitAllowingStateLoss() instead of commit() to solve this problem. Here we look at the difference between the two methods through the source code.

Starting with an instance of the FragmentTransaction class, getSupportFragmentManager(), the source code is as follows:

  1. /** 
  2.  * Return the FragmentManager for interacting with fragments associated 
  3.  * with this activity. 
  4.  */  
  5. public FragmentManager getSupportFragmentManager() {  
  6.     return mFragments;  
  7. }  
This returned mFragments is an instance of the FragmentManagerImpl class, which inherits from the FragmentManager class:
  1. final FragmentManagerImpl mFragments = new FragmentManagerImpl();  
In the FragmentManager class, we also see beginTransaction() as an abstract method to open its implementation class
  1. final class FragmentManagerImpl extends FragmentManager {  
  2.   
  3.     ... ...  
  4.   
  5.     @Override  
  6.     public FragmentTransaction beginTransaction() {  
  7.         return new BackStackRecord(this);  
  8.     }  
  9.   
  10.     .... ...  
  11.   
  12. }  
We see that the method in this implementation class returns an entity of the BackStateRecord class. If we continue to trace the class, we will find that the class actually inherits from FragmentTransaction, and we see the familiar method here:
  1. final class BackStackRecord extends FragmentTransaction implements  
  2.         FragmentManager.BackStackEntry, Runnable {  
  3.   
  4.     public BackStackRecord(FragmentManagerImpl manager) {  
  5.         mManager = manager;  
  6.     }  
  7.   
  8.     public int commit() {  
  9.         return commitInternal(false);  
  10.     }  
  11.   
  12.     public int commitAllowingStateLoss() {  
  13.         return commitInternal(true);  
  14.     }  
  15.       
  16.     int commitInternal(boolean allowStateLoss) {  
  17.         if (mCommitted) throw new IllegalStateException("commit already called");  
  18.         if (FragmentManagerImpl.DEBUG) {  
  19.             Log.v(TAG, "Commit: " + this);  
  20.             LogWriter logw = new LogWriter(TAG);  
  21.             PrintWriter pw = new PrintWriter(logw);  
  22.             dump("  "null, pw, null);  
  23.         }  
  24.         mCommitted = true;  
  25.         if (mAddToBackStack) {  
  26.             mIndex = mManager.allocBackStackIndex(this);  
  27.         } else {  
  28.             mIndex = -1;  
  29.         }  
  30.         mManager.enqueueAction(this, allowStateLoss);  
  31.         return mIndex;  
  32.     }  
  33.   
  34. }  
Finally, we found something useful for us. We omitted other unnecessary code here, leaving only the core code we need to use. We are interested in looking at the source code by ourselves. We also omitted some common methods such as add, remove, replace and so on. We can go back to the topic and see the two methods we are looking for, commit() and commit Allowing State Loss (), which are all called. commitInternal(boolean) allowStateLoss) This method, but the parameters are different. Let's look at the commitInternational method. This method first decides whether commit has been made. This is a simple, direct skip. Finally, it implements the enqueueAction method of the FragmentTransactionImpl class. Okay, don't bother. Let's continue to trace this method:
  1. public void enqueueAction(Runnable action, boolean allowStateLoss) {  
  2.     if (!allowStateLoss) {  
  3.         checkStateLoss();  
  4.     }  
  5.     synchronized (this) {  
  6.         if (mActivity == null) {  
  7.             throw new IllegalStateException("Activity has been destroyed");  
  8.         }  
  9.         if (mPendingActions == null) {  
  10.             mPendingActions = new ArrayList<Runnable>();  
  11.         }  
  12.         mPendingActions.add(action);  
  13.         if (mPendingActions.size() == 1) {  
  14.             mActivity.mHandler.removeCallbacks(mExecCommit);  
  15.             mActivity.mHandler.post(mExecCommit);  
  16.         }  
  17.     }  
  18. }  
In this way, we can see that he first judges the input parameters of commit and commit Allowing State Loss, and then throws the task into the thread queue of activity. Here we focus on checkStateLoss.
  1. private void checkStateLoss() {  
  2.     if (mStateSaved) {  
  3.         throw new IllegalStateException(  
  4.                 "Can not perform this action after onSaveInstanceState");  
  5.     }  
  6.     if (mNoTransactionsBecause != null) {  
  7.         throw new IllegalStateException(  
  8.                 "Can not perform this action inside of " + mNoTransactionsBecause);  
  9.     }  
  10. }  
This method is simple, just a simple judgment, and only calls commit method to execute here, commitAllowingStateLoss skips this step directly, here when we call commit method, the system judges whether the state (mStateSaved) has been saved, and if it has been saved, throws "Can not not be saved" Perform this action after Save Instance State "exception, this is the problem we encounter, but not with the commitAllowingStateLoss method, which corresponds to the activity we analyzed earlier to save the state. When calling commit after the activity save state is completed, the mStateSaved is the saved state, so the exception will be thrown.

Finally, the long talk is over. In fact, it is not so complicated to look back on it. If we follow the source code step by step, we will find out where we have made mistakes. After looking at the source code, we find that it is not so difficult, so easy! _________. Ha-ha

Posted by johnoc on Fri, 17 May 2019 10:42:17 -0700