問題:
我們?cè)谑褂肍ragment的時(shí)候,會(huì)偶爾出現(xiàn)錯(cuò)誤:
IllegalStateException: Can not perform this action after onSaveInstanceState,如下圖:
解決方案:
根據(jù)異常信息Can not perform this action after onSaveInstanceState,可以了解到異常原因:在onSaveInstanceState行為之后,app執(zhí)行某個(gè)不能響應(yīng)的行為而導(dǎo)致異常發(fā)生。這里是指在執(zhí)行onSaveInstanceState之后再調(diào)用FragmentTransaction的commit方法導(dǎo)致異常的發(fā)生。
官方的資料顯示使用commitAllowingStateLoss則不會(huì)導(dǎo)致該異常的發(fā)生。
https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()
Like commit()
but allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user.
源碼剖析
上面已經(jīng)給出了解決方案,使用commitAllowingStateLoss即可。下面從源碼角度分析一下為什么。
1.首先看看FragmentTransaction的兩個(gè)方法:
public abstract int commit();
public abstract int commitAllowingStateLoss();
2.FragmentTransaction中方法的實(shí)現(xiàn):
@Overridepublic
int commit() {
return commitInternal(false);
}
@Override
public int commitAllowingStateLoss() {
return commitInternal(true);
}
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);
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
可以看到當(dāng)我們調(diào)用commit 或者 commitAllowingStateLoss方法的時(shí)候,都會(huì)調(diào)用commitInternal(boolean allowStateLoss)這個(gè)方法,只是傳參不同而已,而在commitInternal方法中最終都會(huì)調(diào)用FragmentManager種的enqueueAction方法:
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}
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);
}
}
看到這里大家都明白了,當(dāng)我們調(diào)用commit的時(shí)候,allowStateLoss為false,這個(gè)時(shí)候會(huì)調(diào)用checkStateLoss,而當(dāng)Activity調(diào)用了onSaveInstanceState后,mStateSaved為True,所以會(huì)導(dǎo)致拋出異常。onSaveInstanceState方法是在該Activity即將被銷毀前調(diào)用,來保存Activity數(shù)據(jù)的,如果在保存完?duì)顟B(tài)后再給它添加Fragment就會(huì)出錯(cuò)。解決辦法就是把commit()方法替換成 commitAllowingStateLoss()就行了,其效果是一樣的。
DialogFragment怎么辦?
然而在DialogFrament中,當(dāng)調(diào)用show方法的時(shí)候,其內(nèi)部實(shí)現(xiàn)為:
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
內(nèi)部的實(shí)現(xiàn)為commit方式,并無commitAllowingStateLoss方式可以選擇,可能因?yàn)橛幸恍┢渌L(fēng)險(xiǎn),Android的開發(fā)團(tuán)隊(duì)沒有為我們提供這個(gè)方法。
解決方案:
1、try catch
@Override
public void show(FragmentManager manager, String tag) {
try{
super.show(manager,tag);
}catch (IllegalStateException ignore){
}
}
2、當(dāng)然使用try catch的方式是比較欠考慮的,可以重寫DialogFragment,使用反射的方式提供showAllowingStateLoss方法:
public void showAllowingStateLoss(FragmentManager manager, String tag){
try {
Field dismissed = DialogFragment.class.getDeclaredField("mDismissed");
dismissed.setAccessible(true);
dismissed.set(this, false);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
try {
Field shown = DialogFragment.class.getDeclaredField("mShownByMe");
shown.setAccessible(true);
shown.set(this, true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commitAllowingStateLoss();
}