告別Can not perform this action after onSaveInstanceState

最近又在應用中遇到Can not perform this action after onSaveInstanceState這個bug傍衡,對于這個bug是有所了解的撒轮,但一下子仍然沒看出來是為什么,因此在解bug的過程中重新梳理一下相關的邏輯牡肉,并做相應的記錄。真正掌握commit的時機,告別這個bug待榔。

  1. 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雕憔。

  2. 問題跟蹤
    跟進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什么時候不行了清蚀。

  1. 案例中的問題
    在項目中導致這個bug的原因有兩個:

    • 以前跟過這個問題,但沒有跟dispatchResume方法爹谭,想當然的以為onResume之后就可以commit了枷邪,但從剛剛的結(jié)論可以知道,并不行诺凡。
    • 由于從B回到A东揣,還得經(jīng)過onStart方法,但由于B的Theme設置成透明的腹泌,因此onStart沒有執(zhí)行嘶卧。
  2. 解決辦法
    在這個案例中,有幾個解決辦法:

    1. 使用commitAllowingStateLoss方法凉袱。
    2. 覆寫onPostResume芥吟、onResumeFragments,在這兩個方法中commit专甩。
  3. 總結(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哺哼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佩抹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子取董,更是在濱河造成了極大的恐慌棍苹,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茵汰,死亡現(xiàn)場離奇詭異枢里,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門栏豺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彬碱,“玉大人,你說我怎么就攤上這事奥洼∠锾郏” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵灵奖,是天一觀的道長嚼沿。 經(jīng)常有香客問我,道長瓷患,這世上最難降的妖魔是什么骡尽? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮尉尾,結(jié)果婚禮上爆阶,老公的妹妹穿的比我還像新娘。我一直安慰自己沙咏,他們只是感情好辨图,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肢藐,像睡著了一般故河。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吆豹,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天鱼的,我揣著相機與錄音,去河邊找鬼痘煤。 笑死凑阶,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的衷快。 我是一名探鬼主播宙橱,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蘸拔!你這毒婦竟也來了师郑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤调窍,失蹤者是張志新(化名)和其女友劉穎宝冕,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邓萨,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡地梨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年菊卷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湿刽。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡的烁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诈闺,到底是詐尸還是另有隱情渴庆,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布雅镊,位于F島的核電站襟雷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏仁烹。R本人自食惡果不足惜耸弄,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卓缰。 院中可真熱鬧计呈,春花似錦、人聲如沸征唬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽总寒。三九已至扶歪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摄闸,已是汗流浹背善镰。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留年枕,地道東北人炫欺。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像熏兄,于是被迫代替她去往敵國和親竣稽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容