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()