Fragment的FragmentTransaction 的commit()和commitAllowingStateLoss()以及commitNow()和commitNowAllowingStateLoss()
android开发中肯定用到过Fragment
1 fragmentManager = getSupportFragmentManager(); 2 3 lifeFragment1 = new FragmentLife(); 4 Bundle bundle = new Bundle(); 5 bundle.putString("extra_test", "FragmentLife1"); 6 lifeFragment1.setArguments(bundle); 7 8 //其实是通过FragmentManagerImpl获取一个BackStackRecord, 9 // 只能在activity存储它的状态(onSaveInstanceState(),当用户要离开activity时)之前调用commit(),如果在存储状态之后调用commit(),将会抛出一个异常。 10 // 这是因为当activity再次被恢复时commit之后的状态将丢失。如果丢失也没关系,那么使用commitAllowingStateLoss()方法。 11 // commit和CommitNow都会抛出异常,如果在onSaveInstanceState()后执行的话。allowStateLoss=false,方法后面会执行检查checkStateLoss(),就是已经保存状态或stop的就会抛出异常IllegalStateException 12 // commitNow不允许addToBackStack,commitNow是不允许加入BackStack中去的,会将mAddToBackStack标志设置为false 13 14 //class BackStackRecord extends FragmentTransaction implements BackStackEntry, OpGenerator 15 FragmentTransaction transaction = fragmentManager.beginTransaction(); 16 transaction.add(R.id.fragment_container, lifeFragment1); 17 // transaction.addToBackStack("frag1"); //设置BackStackRecord的mAddToBackStack标志为true 18 //int类型的返回值,而commitNow是void类型返回值。 19 transaction.commit(); 20 transaction.commitAllowingStateLoss(); 21 //同commit一样调用内部的commitInternal()方法,只不过传递的参数不同,commitAllowStateLoss的allowStateLoss是true,允许丢失状态不做检查,所以不会抛异常。 22 //commit、commitAllowingStateLoss调用了FragmentManagerImpl.enqueueAction的方法,丢进线程队列中 23 24 transaction.commitNow();
这段代码我们经常写,会很熟悉。但有时我们可能会碰到一个异常,信息如下:
1 Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
大意是在activity的onSaveInstanceState调用后再commit的Transaction导致的异常。为了不抛出异常有人建议使用commitAllowingStateLoss来代替commit。
那么commit和commitAllowingStateLoss有什么区别?
1 public int commit() { 2 return commitInternal(false); 3 } 4 5 public int commitAllowingStateLoss() { 6 return commitInternal(true); 7 }
commit和commitAllowingStateLoss都调用了commitInternal()方法,只是一个传了false,一个传了true,接着往下看:
1 int commitInternal(boolean allowStateLoss) { 2 if (mCommitted) { 3 throw new IllegalStateException("commit already called"); 4 } 5 ...... 6 mCommitted = true; 7 if (mAddToBackStack) { 8 mIndex = mManager.allocBackStackIndex(this); 9 } else { 10 mIndex = -1; 11 } 12 mManager.enqueueAction(this, allowStateLoss); 13 return mIndex; 14 }
主要是mManager.enqueueAction(this, allowStateLoss)来执行这个任务,根据传入的参数继续往下走,可以看到:
1 public void enqueueAction(Runnable action, boolean allowStateLoss) { 2 if (!allowStateLoss) { 3 checkStateLoss(); 4 } 5 synchronized (this) { 6 if (mDestroyed || mHost == 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 mHost.getHandler().removeCallbacks(mExecCommit); 15 mHost.getHandler().post(mExecCommit); 16 } 17 } 18 }
可以看到最开始传进来的allowStateLoss在这里只做了检查状态的操作;
1 private void checkStateLoss() { 2 if (mStateSaved) { 3 throw new IllegalStateException("Can not perform this action after onSaveInstanceState"); 5 } 6 if (mNoTransactionsBecause != null) { 7 throw new IllegalStateException("Can not perform this action inside of " + mNoTransactionsBecause); 9 } 10 }
如果activity的状态被保存了,这里再提交就会检查这个状态,符合这个条件就抛出一个异常来终止应用进程。也就是说在activity调用了onSaveInstanceState()之后,再commit一个事务就会出现该异常。那如果不想抛出异常,也可以很简单调用commitAllowingStateLoss()方法来略过这个检查就可以了,但是这是危险的,如果activity随后需要从它保存的状态中恢复,这个commit是会丢失的。因此它仅仅适用在ui状态的改变对用户来说是可以接受的,允许丢失一部分状态。
总结
:
- 在Activity的生命周期方法中提交事务要小心,越早越好,比如onCreate。尽量避免在onActivityResult()方法中提交。
- 避免在异步的回调方法中执行commit,因为他们感知不到当前Activity生命周期的状态。
- 使用commitAllowingStateLoss()代替commit()。相比于异常crash,UI状态的改变对用户来说是可以接受的。
-
如果你需要在Activity执行完onSaveInstanceState()之后还要进行提交,而且不关心恢复时是否会丢失此次提交,那么可以使用
commitAllowingStateLoss()
或commitNowAllowingStateLoss()
。
二、 commitNow以及commitNowAllowingstateLoss()
在API_24版本FragmentTranslation里添加了该两个方法:
下面拿commitNow为例:
1 public void commitNow() { 2 this.disallowAddToBackStack(); 3 this.mManager.execSingleAction(this, false); 4 }
该方法不支持加入BackStack回退栈中,disallowAddToBackStack()。
源码没有再使用Handler,而是直接执行(源码如下)
1 public void execSingleAction(FragmentManagerImpl.OpGenerator action, boolean allowStateLoss) { 2 if (!allowStateLoss || this.mHost != null && !this.mDestroyed) { 3 this.ensureExecReady(allowStateLoss); 4 if (action.generateOps(this.mTmpRecords, this.mTmpIsPop)) { 5 this.mExecutingActions = true; 6 7 try { 8 this.removeRedundantOperationsAndExecute(this.mTmpRecords, this.mTmpIsPop); 9 } finally { 10 this.cleanupExec(); 11 } 12 } 13 14 this.doPendingDeferredStart(); 15 this.burpActive(); 16 } 17 }
官方更推荐使用commitNow()
和commitNowAllowingStateLoss()
来代替先执行commit()/commitAllowingStateLoss()