關于FragmentTransaction以前用到過但是了解不全面,只是會簡單使用。今天再次碰到所在在此將它詳細記錄:通過兩篇比較好的文章總結一下,相信看完這兩篇文章你暫時的問題都會得到解決,如果還有什么疑問大家可以留言討論。
我轉的第一篇文章是作者對別人的文章進一步修改得到的更易懂的作品:
之前在使用Fragment的時候偶爾會有這么一個報錯,Can not perform this action after onSaveInstanceState,意思為無法再onSaveInstanceState之后執行該操作,這個操作就是指commit(),之前也沒怎么在意,后來通過查看源碼去了解了一下這個問題,以下是對這個問題的解析及對應解決辦法的對比。
Fragment是我們經常用到的東西,常用的操作有添加(add)、移除(remove)、替換(replace)等,這些操作組成一個集合transaction,我們在通過調用getSupportFragmentManager().beginTransaction()來獲取這個FragmentTransaction類的實例來管理這些操作,將他們存進由activity管理的back stack中,這樣我們就可以進行fragment變化的回退操作,也可以這樣去獲取FragmentTransaction類的實例:
FragmentManagermFragmentManager=getSupportFragmentManager();FragmentTransactionmFragmentTransaction=mFragmentManager.beginTransaction(); 為什么我們會有這種報錯呢,因為我們在使用add(),remove(),replace()等方法將Fragment的變化添加進去,然后在通過commit去提交這些變化(另外,在commit之前可以去調用addToBackState()方法,將這些變化加入到activity管理的back stack中去,這樣用戶調用返回鍵就可以回退這些變化了),提交完成之后這些變化就會應用到我們的Fragment中去。但是,這個commit()方法,你只能在avtivity存儲他的狀態之前調用,也就是onSaveInstanceState(),我們都知道activity有一個保存狀態的方法和恢復狀態的方法,這個就不詳細解釋了,在onSaveInstanceState()方法之后去調用commit(),就會拋出我們遇到的這個異常,這是因為在onSaveInstanceState()之后調用commit()方法,這些變化就不會被activity存儲,即這些狀態會被丟失,但我們可以去用commitAllowingStateLoss()這個方法去代替commit()來解決這個為題,下面我們通過源碼去看這兩個方法的區別。
首先從我們獲取FragmentTransaction類的實例開始,即getSupportFragmentManager(),源碼是這樣的:
/***ReturntheFragmentManagerforinteractingwithfragmentsassociated
*withthisactivity.*/
publicFragmentManagergetSupportFragmentManager(){returnmFragments;
}
而這個返回的mFragments是一個FragmentManagerImpl類 的實例,他繼承自FragmentManager這個類:
finalFragmentManagerImplmFragments=newFragmentManagerImpl();
我們在FragmentManager這個類中還看到beginTransaction()這個抽象方法,打開他的實現類
finalclassFragmentManagerImplextendsFragmentManager{
......
@OverridepublicFragmentTransactionbeginTransaction(){
returnnewBackStackRecord(this);}
.......
} 我們看到這個實現類中的該方法是返回一個BackStateRecord類的實體,我們繼續去追蹤這個類,就會發現,這個類其實是繼承自FragmentTransaction的,并且,我們在這里看到我們熟悉的方法:
finalclassBackStackRecordextendsFragmentTransactionimplementsFragmentManager.BackStackEntry,Runnable{
publicBackStackRecord(FragmentManagerImplmanager){
mManager=manager;}
publicintcommit(){
returncommitInternal(false);}
publicintcommitAllowingStateLoss(){
returncommitInternal(true);}
intcommitInternal(booleanallowStateLoss){
if(mCommitted)thrownewIllegalStateException("commitalreadycalled");if(FragmentManagerImpl.DEBUG){
Log.v(TAG,"Commit:"+this);LogWriterlogw=newLogWriter(TAG);
PrintWriterpw=newPrintWriter(logw);dump("",null,pw,null);
}mCommitted=true;
if(mAddToBackStack){mIndex=mManager.allocBackStackIndex(this);
}else{mIndex=-1;
}mManager.enqueueAction(this,allowStateLoss);
returnmIndex;}
}
終于找到了我們有用的東西了,這里省略了其他不必要的代碼,只留下我們需要用的核心代碼,有興趣可以自己去查看源碼,這里還有省略掉我們常用的add、remove、replace等方法,回歸正題,看我們要找的兩個方法commit()和commitAllowingStateLoss(),他們都調用了commitInternal(boolean allowStateLoss)這個方法,只是傳入的參數不同,我們去看commitInternal方法,該方法首先去判斷是否已經commit,這個簡單,直接跳過,最后他執行的是FragmentTransactionImpl類的enqueueAction方法,好,不要嫌麻煩,我們繼續去追蹤這個方法:
publicvoidenqueueAction(Runnableaction,booleanallowStateLoss){if(!allowStateLoss){
checkStateLoss();}
synchronized(this){if(mActivity==null){
thrownewIllegalStateException("Activityhasbeendestroyed");}
if(mPendingActions==null){mPendingActions=newArrayList();
}mPendingActions.add(action);
if(mPendingActions.size()==1){mActivity.mHandler.removeCallbacks(mExecCommit);
mActivity.mHandler.post(mExecCommit);}
}}
通過這個方法我們可以看到,他首先去根據commit和commitAllowingStateLoss這兩個方法傳入的參數不同去判斷,然后將任務扔進activity的線程隊列中,這里我們重點去看的是checkStateLoss()這個方法:
privatevoidcheckStateLoss(){if(mStateSaved){
thrownewIllegalStateException("CannotperformthisactionafteronSaveInstanceState");
}if(mNoTransactionsBecause!=null){
thrownewIllegalStateException("Cannotperformthisactioninsideof"+mNoTransactionsBecause);
}} 這個方法很簡單,就只是一個簡單的判斷而已,并且只有調用commit方法才會執行這里,commitAllowingStateLoss則直接跳過這步,這里我們調用commit方法時,系統系判斷狀態(mStateSaved)是否已經保存,如果已經保存,則拋出"Can not perform this action after onSaveInstanceState"異常,這就是我們遇到的問題了,而用commitAllowingStateLoss方法則不會這樣,這就與我們之前分析的activity去保存狀態對應上了,在activity保存狀態完成之后調用commit時,這個mStateSaved就是已經保存狀態,所以會拋出異常。
通過第一篇相信大多數人知道了FragmentTransaction的commit方法與commitAllowingStateLoss方法的區別以及前者產生異常的原因,同時也知道
FragmentTransaction.addToBackStack(String name)方法的作用是將transactions存到activity的backstack中,以便進行回退。
第二篇文章是一篇比較有深度的文章,相信作者是個牛人,要說明的東西很好理解:
Fragment Transactions和Activity狀態丟失
2014/08/19 · Android, 開發 · 1 評論 · 安卓開發, 異常處理
分享到:18 微信小程序入門與實戰常用組件 API 開發技巧 項目實戰扛得住的MySQL數據庫架構算法與數據結構C++精解所向披靡的響應式開發本文由 伯樂在線 - 獨孤昊天 翻譯。未經許可,禁止轉載! 英文出處:androiddesignpatterns。歡迎加入翻譯組。
下面的堆棧跟蹤和異常代碼,自從Honeycomb的初始發行版本就一直使得StackOverflow很迷惑。
Java
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的狀態保存之后,嘗試去提交一個FragmentTransaction。這種現象被稱為活動狀態丟失(Activity State Loss)。然而,在我們了解這種異常的真正含義之前,讓我們先看看當onSaveInstanceState()函數被調用的時候到底發生了什么。
正如最近我在關于Binders & Death Recipients博客里面討論的那樣,Android應用在Android運行環境里很難決定自己的命運。Android系統可以在任何時候通過結束一個進程以釋放內存,而且background activities可能在沒有任何警告的情況下被清理。為了確保這種不確定的行為對于用戶是透明的,在Activity可以銷毀之前,通過調用onSaveInstanceState()方法,架構給每個Activity一個保存自身狀態的機會。在重新加載已保存的狀態時,對于foreground和background Activities的切換,為用戶帶來了無縫切換的體驗。用戶不用去關心這個Activity是否被系統銷毀了。
在框架調用onSaveInstanceState()方法時,給這個方法傳遞了一個Bundle對象。Activity可以通過這個對象來存儲它的狀態,而且Activity把它的dialogs、fragments以及views的狀態都保存在這個對象里面。當這個函數返回時,系統打包這個Bundle對象通過一個Binder接口傳遞給系統服務處理,然后它會被安全的存儲下來。當系統決定重新創建這個Activity的時候,它會給這個應用傳回一個相同的Bundle對象,通過這個對象可以重新裝載Activity銷毀時的狀態。
那為什么會拋出這個異常呢?這個問題源于這樣的事實,Bundle對象代表一個Activity在調用onSaveInstanceState()方法的一個瞬間快照,僅此而已。這意味著,當你在onSaveInstanceState()方法調用后會調用FragmentTransaction的commit方法。這個transaction將不會被記住,因為它沒有在第一時間記錄為這個Activity的狀態的一部分。從用戶的角度來看,這個transaction將會丟失,可能導致UI狀態丟失。為了保證用戶的體驗,Android不惜一切代價避免狀態的丟失。因此,無論什么時候發生,都將簡單的拋出一個IllegalStateException異常。
什么時候會拋出這個異常?
如果之前你遇到過這個異常,也許你已經注意到異常拋出的時間在不同的版本平臺有細微的差別。也許你會發現,老版本的機器拋出異常的頻率更低,或者你的應用使用Support Library比使用官方的框架類的時候更容易拋出異常。這個細微的區別已經導致一些人在猜測Support Library有bug,是不值得相信的。然而,這樣的猜想完全錯誤。
這些細微區別存在的原因是源于Honeycomb上對于Activity生命周期所做的巨大改變。在Honeycomb之前,Activity直到暫停后才考慮被銷毀。這意味著在onPause()方法之前onSaveInstanceState()方法被立即調用。然而,從Honeycomb開始,考慮銷毀Activity只能是在他們停止之后,這意味著onSaveInstanceState()方法現在是在onStop()方法之前調用,以此代替在onPause()方法之前調用。這些不同總結如下表:
Honeycomb之前的版本Honeycomb及更新的版本
Activities會在onPause()調用前被結束?NONO
Activities會在onStop()調用前被結束?YESNO
onSaveInstanceState(Bundle)會在哪些方法調用前被執行?onPause()onStop()
作為Activity生命周期已做的細微改變的結果,Support Library有時候需要根據平臺的版本來改變它的行為。比如,在Honeycomb及以上的設備中,每當一個commit方法在onSaveInstanceState()方法之后調用時,都會拋出一個異常來提醒開發者狀態丟失發生了。然而,在Honeycomb之前的設備上,每次它發生時并拋出異常將更受限制,他們的onSaveInstanceState()方法在Activity的生命周期中更早調用,結果更容易發生狀態丟失。Android團隊被迫做了一個折中的辦法:為了更好的與老版本平臺交互,老的設備不得不接受偶然狀態丟失可能發生在onPause()方法和onStop()方法之間。Support Library在不同平臺的行為總結如下表:
Honeycomb之前的版本Honeycomb及更新的版本
commit()在onPause()前被調用OKOK
commit()在onPause()和onStop()執行中間被調用STATE LOSSOK
commit()在onStop()之后被調用EXCEPTIONEXCEPTION
如何避免拋出異常?
一旦你了解了到底發生了什么,避免發生Activity狀態丟失將會很簡單。如果你讀了這篇博客,那么很幸運你更好的了解了Support Library是怎么工作的,以及在你的應用中避免狀態丟失為什么如此的重要。假如你查看這個博客是為了查找快速解決的辦法,那么,當你在你的應用中使用FragmentTransactions的時候,應牢記以下的這些建議:
建議一
當你在Activity生命周期函數里面提交transactions的時候要小心。大部分的應用僅僅在onCreate()方法被調用的開始時間提交transactions,或者在相應用戶輸入的時候,因此將不可能碰到任何問題。然而,當你的transactions在其他的Activity生命周期函數提交,如onActivityResult()、onStart()和onResume(),事情將會變得微妙。例如,你不應該在FragmentActivity的onResume()方法中提交transactions。因為有些時候這個函數可以在Activity的狀態恢復前被調用(可以查看相關文檔了解更多信息)。如果你的應用要求在除onCreate()函數之外的其他Activity生命周期函數中提交transaction,你可以在FragmentActivity的onResumeFragments()函數或者Activity的onPostResume()函數中提交。這兩個函數確保在Activity恢復到原始狀態之后才會被調用,從而避免了狀態丟失的可能性。(示例:看看我對this StackOverflow question的回答,來想想如何提交FragmentTransactions作為Activity的onActivityResult方法被調用的響應)。
建議二
避免在異步回調函數中提交transactions。包括常用的方法,比如AsyncTask的onPostExecute方法和LoaderManager.LoaderCallbacks的onLoadFinished方法。在這些方法中執行transactions的問題是,當他們被調用的時候,他們完全沒有Activity生命周期的當前狀態。例如,考慮下面的事件序列:
一個Activity執行一個AsyncTask。用戶按下“Home”鍵,導致Activity的onSaveInstanceState()和onStop()方法被調用。AsyncTask完成并且onPostExecute方法被調用,而它沒有意識到Activity已經結束了。在onPostExecute函數中提交的FragmentTransaction,導致拋出一個異常。
一般來說,避免這種類型異常的最好辦法就是不要在異步回調函數中提交transactions。Google工程師似乎同意這個信條。根據Android Developers group上的這篇文章,Android團隊認為UI主要的改變,源于從異步回調函數提交FragmentTransactions引起不好的用戶體驗。如果你的應用需要在這些回調函數中執行transaction而沒有簡單的方法可以確保這個回調函數不好在onSaveInstanceState()之后調用。你可能需要訴諸于使用commitAllowingStateLoss方法,并且處理可能發生的狀態丟失。(可以看看StackOverflow上的另外兩篇文章,這一篇和另一篇)。
建議三
作為最后的辦法,使用commitAllowingStateLoss()函數。commit()函數和commitAllowingStateLoss()函數的唯一區別就是當發生狀態丟失的時候,后者不會拋出一個異常。通常你不應該使用這個函數,因為它意味可能發生狀態丟失。當然,更好的解決方案是commit函數確保在Activity的狀態保存之前調用,這樣會有一個好的用戶體驗。除非狀態丟失的可能無可避免,否則就不應該使用commitAllowingStateLoss()函數。
第二篇作者給出了使用"提交"的時機,以及使用建議,并總結出:
commit()函數和commitAllowingStateLoss()函數的唯一區別就是當發生狀態丟失的時候,后者不會拋出一個異常。通常你不應該使用這個函數,因為它意味可能發生狀態丟失。
看文倉www.kanwencang.com網友整理上傳,為您提供最全的知識大全,期待您的分享,轉載請注明出處。
歡迎轉載:http://www.kanwencang.com/bangong/20170111/85702.html
文章列表