最近出現了一個很奇怪的問題,問題異常日誌以下:java
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2053)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2079)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:678)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:632)
at android.support.v4.app.DialogFragment.dismissInternal(DialogFragment.java:223)
at android.support.v4.app.DialogFragment.dismiss(DialogFragment.java:190)
複製代碼
剛開始覺得是 DialogFragment 調用 show 的時候,沒有判斷 Activity 是否已經 Finish 了。結果發如今項目代碼裏是作了判斷的,才知道確定沒這麼簡單。android
FragmentManagerImpl.checkStateLossbash
private void checkStateLoss() {
//異常拋出的地方
if (mStateSaved) {
throw new IllegalStateException("Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException("Can not perform this action inside of " + mNoTransactionsBecause);
}
}
複製代碼
FragmentManagerImpl. enqueueAction服務器
/**
* Adds an action to the queue of pending actions.
*
* @param action the action to add
* @param allowStateLoss whether to allow loss of state information
* @throws IllegalStateException if the activity has been destroyed
*/
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
//注意這裏的allowStateLoss 它決定了checkStateLoss方法是否進行
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<>();
}
mPendingActions.add(action);
scheduleCommit();
}
}
複製代碼
BackStackRecord.commitInternalapp
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(TAG);
PrintWriter pw = new PrintWriter(logw);
dump(" ", null, pw, null);
pw.close();
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
//重點
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
複製代碼
BackStackRecord.commitide
public int commit() {
//這裏傳入的就是allowStateLoss參數
return commitInternal(false);
}
複製代碼
DialogFragment.showui
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
//沒想到的時候DialogFragment的show方法會調用commit() 導火索
ft.commit();
}
複製代碼
目前來看是由於 DialogFragment 的 show 方法致使強行檢查 Fragment 的狀態,而剛好在檢查的時候狀態已經被保存致使 FragmentManager 的標記位 mStateSaved = true 。this
當用戶在進入首頁模塊時直接按 Home 鍵或者遇到內存不足等特殊狀況下觸發了onSaveInstanceState 致使 FragmentManager 進行「智能」保存當前 Activity 中 Fragment 的狀態,由於 DialogFragment (廣告彈窗)須要先從服務器拉取數據,因此以後在 show 的時候去 checkStateLoss 時觸發了異常。spa
Dialog
,再也不使用 DialogFragment
;Fragment
的相關狀態進行保存和維護,能夠在相關 Activity
中複寫 onSaveInstance
不進行 super
回調便可(不建議使用此方案);DialogFragment
中的 show
方法,並使用 commitAllowStateLoss
提交,以下代碼(推薦使用此方案)。// 方案3代碼示例
override fun show(manager: FragmentManager?, tag: String?) {
// super.show(manager, tag)
val ft = manager?.beginTransaction()
ft?.add(this, tag)
ft?.commitAllowingStateLoss()
}
複製代碼