最近又在應用中遇到Can not perform this action after onSaveInstanceState這個bug傍衡,對于這個bug是有所了解的撒轮,但一下子仍然沒看出來是為什么,因此在解bug的過程中重新梳理一下相關的邏輯牡肉,并做相應的記錄。真正掌握commit的時機,告別這個bug待榔。
bug發(fā)生的背景
bug發(fā)生的地方是這樣的,Activity A(后續(xù)簡稱A)包含幾個Fragment流济,可以點擊跳轉(zhuǎn)到Activity B锐锣,選擇某一項之后返回Activity A顯示相應的Fragment腌闯。因此在A的onResume方法中調(diào)用了commit。結(jié)果就是返回A的時候就發(fā)生了Crash雕憔。問題跟蹤
跟進commit的代碼里面很容易可以找到問題的原因是因為在checkStateLoss方法中姿骏,mStateSaved的值為true。(由于篇幅問題橘茉,源碼中略去部分無關代碼工腋,完整源碼請自行查看)
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);
}
}
所以我們就需要弄清楚mStateSaved是什么,什么時候是true畅卓,什么時候是false擅腰。對mStateSaved變量進行搜索∥膛耍可以發(fā)現(xiàn)趁冈,幾乎都是和生命周期相關的方法。
Parcelable saveAllState() {
if (HONEYCOMB) {
mStateSaved = true;
}
}
public void noteStateNotSaved() {
mStateSaved = false;
}
public void dispatchCreate() {
mStateSaved = false;
mExecutingActions = true;
moveToState(Fragment.CREATED, false);
mExecutingActions = false;
}
這里不貼所有的源碼了拜马,將mStateSaved設置為true的有saveAllState渗勘、dispatchStop兩個方法,而在noteStateNotSaved俩莽、dispatchCreate旺坠、dispatchActivityCreated、dispatchStart扮超、dispatchResume等方法中置為false取刃。
對這些方法進行跟蹤
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState();
}
@Override
protected void onStop() {
super.onStop();
mStopped = true;
mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
mFragments.dispatchStop();
}
可以發(fā)現(xiàn)在onStop和onSaveInstanceState方法中進行調(diào)用,因此在onStop和onSaveInstanceSate之后就不能commit了出刷。再跟蹤設置為true的方法:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mFragments.noteStateNotSaved();
}
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mFragments.noteStateNotSaved();
}
protected void onStart() {
if (!mCreated) {
mCreated = true;
mFragments.dispatchActivityCreated();
}
mFragments.dispatchStart();
}
在onActivityResult璧疗、onNewIntent調(diào)用noteStateNotSaved,在onCreate中調(diào)用dispatchCreate馁龟,在onStart中調(diào)用dispatchActivityCreated和dispatchStart崩侠。onResume有點特殊,單獨提一下坷檩。
protected void onResumeFragments() {
mFragments.dispatchResume();
}
onResume在onResumeFragments調(diào)用却音。而onResumeFragments在哪里調(diào)用可以繼續(xù)跟一下。
@Override
protected void onPostResume() {
super.onPostResume();
mHandler.removeMessages(MSG_RESUME_PENDING);
onResumeFragments();
mFragments.execPendingActions();
}
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REALLY_STOPPED:
if (mStopped) {
doReallyStop(false);
}
break;
case MSG_RESUME_PENDING:
onResumeFragments();
mFragments.execPendingActions();
break;
default:
super.handleMessage(msg);
}
}
};
@Override
protected void onResume() {
super.onResume();
mHandler.sendEmptyMessage(MSG_RESUME_PENDING);
mResumed = true;
mFragments.execPendingActions();
}
onResumeFragments調(diào)用的地方有兩個淌喻,一個是在onPostResume調(diào)用僧家,一個在handler中,而發(fā)送MSG_RESUME_PENDING消息的地方則在onResume裸删“斯埃可以得出結(jié)論onResume沒有立刻將mStateSaved設置為false(敲黑板)。
到了這里,結(jié)合Activity的生命周期我們就基本知道肌稻,什么時候可以commit什么時候不行了清蚀。
-
案例中的問題
在項目中導致這個bug的原因有兩個:- 以前跟過這個問題,但沒有跟dispatchResume方法爹谭,想當然的以為onResume之后就可以commit了枷邪,但從剛剛的結(jié)論可以知道,并不行诺凡。
- 由于從B回到A东揣,還得經(jīng)過onStart方法,但由于B的Theme設置成透明的腹泌,因此onStart沒有執(zhí)行嘶卧。
-
解決辦法
在這個案例中,有幾個解決辦法:- 使用commitAllowingStateLoss方法凉袱。
- 覆寫onPostResume芥吟、onResumeFragments,在這兩個方法中commit专甩。
總結(jié)
這一類問題很多人喜歡直接使用commitAllowingStateLoss方法钟鸵,這個方法和commit的區(qū)別就是不調(diào)用checkStateLoss方法,因此不會有這個問題涤躲,是個萬金油方法棺耍,但不建議使用。原因得從這個機制說起种樱,在Activity在異常的情況下被關閉烈掠,會調(diào)用onSaveInstanceState保存狀態(tài),如果在onSaveInstanceState之后commit缸托,則commit的內(nèi)容狀態(tài)得不到保存。
因此還是建議最好還是按照生命周期瘾蛋,合理的使用commit俐镐。
另一個注意點就是不要在子線程中commit,在線程中commit容易忽視Activity的生命周期而出現(xiàn)bug哺哼。