關于FragmentTransaction之前用到過但是了解不全面,只是會簡單使用。今天再次碰到所在在此將它詳細記錄:通過兩篇比較好的文章總結1下,相信看完這兩篇文章你暫時的問題都會得到解決,如果還有甚么疑問大家可以留言討論。
我轉的第1篇文章是作者對他人的文章進1步修改得到的更容易懂的作品:
之前在使用Fragment的時候偶爾會有這么1個報錯,Can not perform this action after onSaveInstanceState,意思為沒法再onSaveInstanceState以后履行該操作,這個操作就是指commit(),之前也沒怎樣在乎,后來通過查看源碼去了解了1下這個問題,以下是對這個問題的解析及對應解決辦法的對照。
Fragment是我們常常用到的東西,經常使用的操作有添加(add)、移除(remove)、替換(replace)等,這些操作組成1個集合transaction,我們在通過調用getSupportFragmentManager().beginTransaction()來獲得這個FragmentTransaction類的實例來管理這些操作,將他們存進由activity管理的back stack中,這樣我們就能夠進行fragment變化的回退操作,也能夠這樣去獲得FragmentTransaction類的實例:
首先從我們獲得FragmentTransaction類的實例開始,即getSupportFragmentManager(),源碼是這樣的:
通過第1篇相信大多數人知道了FragmentTransaction的commit方法與commitAllowingStateLoss方法的區(qū)分和前者產生異常的緣由,同時也知道
FragmentTransaction.addToBackStack(String name)方法的作用是將transactions存到activity的backstack中,以便進行回退。 第2篇文章是1篇比較有深度的文章,相信作者是個牛人,要說明的東西很好理解:Fragment Transactions和Activity狀態(tài)丟失
本文由 伯樂在線 - 獨孤昊天 翻譯。未經許可,制止轉載! 英文出處:androiddesignpatterns。歡迎加入翻譯組。下面的堆棧跟蹤和異常代碼,自從Honeycomb的初始發(fā)行版本就1直使得StackOverflow很迷惑。
123456 ; html-script: false ]java.lang.IllegalStateException:Can not perform this action after onSaveInstanceStateat android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)這篇博客將會解釋,這個異常在甚么時候產生和為何會產生?并且提供幾種方法讓這類異常不會產生在你的利用中。
為何會拋出這個異常?
這類異常的出現是由于,在Activity的狀態(tài)保存以后,嘗試去提交1個FragmentTransaction。這類現象被稱為活動狀態(tài)丟失(Activity State Loss)。但是,在我們了解這類異常的真正含義之前,讓我們先看看當
onSaveInstanceState()
函數被調用的時候到底產生了甚么。正如最近我在關于Binders & Death Recipients博客里面討論的那樣,Android利用在Android運行環(huán)境里很難決定自己的命運。Android系統可以在任什么時候候通過結束1個進程以釋放內存,而且background activities可能在沒有任何正告的情況下被清算。為了確保這類不肯定的行動對用戶是透明的,在Activity可以燒毀之前,通過調用
onSaveInstanceState()
方法,架構給每一個Activity1個保存本身狀態(tài)的機會。在重新加載已保存的狀態(tài)時,對foreground和background Activities的切換,為用戶帶來了無縫切換的體驗。用戶不用去關心這個Activity是不是被系統燒毀了。在框架調用
onSaveInstanceState()
方法時,給這個方法傳遞了1個Bundle對象。Activity可以通過這個對象來存儲它的狀態(tài),而且Activity把它的dialogs、fragments和views的狀態(tài)都保存在這個對象里面。當這個函數返回時,系統打包這個Bundle對象通過1個Binder接口傳遞給系統服務處理,然后它會被安全的存儲下來。當系統決定重新創(chuàng)建這個Activity的時候,它會給這個利用傳回1個相同的Bundle對象,通過這個對象可以重新裝載Activity燒毀時的狀態(tài)。那為何會拋出這個異常呢?這個問題源于這樣的事實,Bundle對象代表1個Activity在調用
onSaveInstanceState()
方法的1個瞬間快照,僅此而已。這意味著,當你在onSaveInstanceState()
方法調用后會調用FragmentTransaction的commit方法。這個transaction將不會被記住,由于它沒有在第1時間記錄為這個Activity的狀態(tài)的1部份。從用戶的角度來看,這個transaction將會丟失,可能致使UI狀態(tài)丟失。為了保證用戶的體驗,Android不惜1切代價避免狀態(tài)的丟失。因此,不管甚么時候產生,都將簡單的拋出1個IllegalStateException異常。甚么時候會拋出這個異常?
如果之前你遇到過這個異常,或許你已注意到異常拋出的時間在不同的版本平臺有細微的差別。或許你會發(fā)現,老版本的機器拋出異常的頻率更低,或你的利用使用Support Library比使用官方的框架類的時候更容易拋出異常。這個細微的區(qū)分已致使1些人在猜想Support Library有bug,是不值得相信的。但是,這樣的料想完全毛病。
這些細微區(qū)分存在的緣由是源于Honeycomb上對Activity生命周期所做的巨大改變。在Honeycomb之前,Activity直到暫停后才斟酌被燒毀。這意味著在
onPause()
方法之前onSaveInstanceState()
方法被立即調用。但是,從Honeycomb開始,斟酌燒毀Activity只能是在他們停止以后,這意味著onSaveInstanceState()
方法現在是在onStop()
方法之前調用,以此代替在onPause()
方法之前調用。這些不同總結以下表:
Honeycomb之前的版本 | Honeycomb及更新的版本 | |
Activities會在onPause()調用前被結束? | NO | NO |
Activities會在onStop()調用前被結束? | YES | NO |
onSaveInstanceState(Bundle)會在哪些方法調用前被履行? | onPause() | onStop() |
作為Activity生命周期已做的細微改變的結果,Support Library有時候需要根據平臺的版本來改變它的行動。比如,在Honeycomb及以上的裝備中,每當1個commit方法在onSaveInstanceState()
方法以后調用時,都會拋出1個異常來提示開發(fā)者狀態(tài)丟失產生了。但是,在Honeycomb之前的裝備上,每次它產生時并拋出異常將更受限制,他們的onSaveInstanceState()
方法在Activity的生命周期中更早調用,結果更容易產生狀態(tài)丟失。Android團隊被迫做了1個折衷的辦法:為了更好的與老版本平臺交互,老的裝備不能不接受偶然狀態(tài)丟失可能產生在onPause()
方法和onStop()
方法之間。Support Library在不同平臺的行動總結以下表:
Honeycomb之前的版本 | Honeycomb及更新的版本 | |
commit()在onPause()前被調用 | OK | OK |
commit()在onPause()和onStop()履行中間被調用 | STATE LOSS | OK |
commit()在onStop()以后被調用 | EXCEPTION | EXCEPTION |
1旦你了解了到底產生了甚么,避免產生Activity狀態(tài)丟失將會很簡單。如果你讀了這篇博客,那末很榮幸你更好的了解了Support Library是怎樣工作的,和在你的利用中避免狀態(tài)丟失為何如此的重要。假設你查看這個博客是為了查找快速解決的辦法,那末,當你在你的利用中使用FragmentTransactions的時候,應牢記以下的這些建議:
當你在Activity生命周期函數里面提交transactions的時候要謹慎。大部份的利用僅僅在onCreate()
方法被調用的開始時間提交transactions,或在相利用戶輸入的時候,因此將不可能碰到任何問題。但是,當你的transactions在其他的Activity生命周期函數提交,如onActivityResult()
、onStart()
和onResume()
,事情將會變得奧妙。例如,你不應當在FragmentActivity的onResume()
方法中提交transactions。由于有些時候這個函數可以在Activity的狀態(tài)恢復前被調用(可以查看相干文檔了解更多信息)。如果你的利用要求在除onCreate()
函數以外的其他Activity生命周期函數中提交transaction,你可以在FragmentActivity的onResumeFragments()
函數或Activity的onPostResume()
函數中提交。這兩個函數確保在Activity恢復到原始狀態(tài)以后才會被調用,從而避免了狀態(tài)丟失的可能性。(示例:看看我對this StackOverflow question的回答,來想一想如何提交FragmentTransactions作為Activity的onActivityResult方法被調用的響應)。
避免在異步回調函數中提交transactions。包括經常使用的方法,比如AsyncTask的onPostExecute方法和LoaderManager.LoaderCallbacks的onLoadFinished方法。在這些方法中履行transactions的問題是,當他們被調用的時候,他們完全沒有Activity生命周期確當前狀態(tài)。例如,斟酌下面的事件序列:
onSaveInstanceState()
和onStop()
方法被調用。1般來講,避免這類類型異常的最好辦法就是不要在異步回調函數中提交transactions。Google工程師似乎同意這個信條。根據Android Developers group上的這篇文章,Android團隊認為UI主要的改變,源于從異步回調函數提交FragmentTransactions引發(fā)不好的用戶體驗。如果你的利用需要在這些回調函數中履行transaction而沒有簡單的方法可以確保這個回調函數不好在onSaveInstanceState()
以后調用。你可能需要訴諸于使用commitAllowingStateLoss方法,并且處理可能產生的狀態(tài)丟失。(可以看看StackOverflow上的另外兩篇文章,這1篇和另外一篇)。
作為最后的辦法,使用commitAllowingStateLoss()
函數。commit()
函數和commitAllowingStateLoss()
函數的唯1區(qū)分就是當產生狀態(tài)丟失的時候,后者不會拋出1個異常。通常你不應當使用這個函數,由于它意味可能產生狀態(tài)丟失。固然,更好的解決方案是commit函數確保在Activity的狀態(tài)保存之前調用,這樣會有1個好的用戶體驗。除非狀態(tài)丟失的可能無可避免,否則就不應當使用commitAllowingStateLoss()
函數。
commit()
函數和commitAllowingStateLoss()
函數的唯1區(qū)分就是當產生狀態(tài)丟失的時候,后者不會拋出1個異常。通常你不應當使用這個函數,由于它意味可能產生狀態(tài)丟失。