IllegalStateException: Can not perform this action after onSaveInstanceState

下面自从Honeycomb发布后,下面栈跟踪信息和异常信息已经困扰了StackOverFlow很久了。
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState atandroid.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager. at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager. at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord. at android.support.v4.app.BackStackRecord.commit(BackStackRecord.

这篇文章会解释这个异常什么时候会抛出以及原因,并且会以一些建议收尾。这些建议会帮助你不会因为这个异常导致程序崩溃。

**
异常原因:Fragment提交transaction导致state loss异常
处理方法:
​在FragmentTransactions调用的commit()方法替换成commitAllowingStateLoss()方法即可。(在onCreate()或者为了响应用户输入的时候调用一次commit()不会抛这个崩溃)
​**

这个异常为什么会抛出?

这个异常抛出的原因是因为你尝试着在Activity的状态已经保存后commit一个FragmentTransaction,导致了一个现象叫做Activity state loss。在我们深入细节之前,让我们先看看在onSaveInstanceState()调用后发生了什么。Android应用程序在Android 运行时系统中只有很小的控制权。Android系统为了释放内存可以在任意时刻停止进程,然后处于后台的Activity就会被毫无警告地杀掉。为了保证有时候因此引起的不稳定行为能避免用户知道,Android框架给每一个Activity通过调用onSaveInstanceState()来保存自己状态的机会,它会在Activity可能被销毁之前调用。当后面恢复状态的时候,用户不会感觉到Activity已经被系统杀掉了,而会感觉前台和后台的Activity无缝切换。

当Android框架调用onSaveInstanceState(),它将一个Bundle对象通过这个方法传递,以便Activity后面恢复状态。Activity可以将它的Dialog、fragment以及view的状态保存在Bundle中。当这个方法返回的时候,系统通过Binder结果打包Bundle对象然后传给系统服务进程。系统服务进程负责保证Bundle对象安全地保存下来。当系统后面决定重新创建Activity的获释后,它就会将相同的Bundle对象发挥应用程序,以便于用它来回复旧的Activity状态。

所以为什么这个异常随后抛出?这个问题导致的原因是因为那些Bundle对象代表Activity在onSaveInstanceState()被调用那时候的一个快照,没更多了。这就意味着当你在onSaveInstanceState()之后调用FragmentTransaction#commit()的时候,transation不会被记录。因为它不会作为之前Activity的状态被保存。从用户的角度来说,这个transaction就像丢失了,导致UI状态意外的丢失。为了保证用户体验,Android不计一切代价避免状态丢失,也就是当它发生的时候简单地抛出一个IllegalStateException。

这个异常什么时候会抛出?

如果你之前已经碰到过这个异常,你可能会注意到异常抛出的时机因为不同的Android版本而不一致。比如,如可能会发现老版本的设备上,这个异常抛出比较不频繁,或者当你的程序中使用support library而不是官方框架中的类时更容易触发这个异常。这些轻微的一致让很多人都以为support library有bug,不值得信任。然而,这些假设都不是正确的。

这些轻微的不一致是因为在Honeycomb版本中的Activity生命周期有了重要的变化。Honeycomb之前的版本,activity被认为在pause之前都不会被杀掉,这意味着onSaveInstanceState()会在onPause()之前被调用。从HoneyComb开始,Activity被认为只会在stopped只会被杀掉,意味着onSaveInstanceState()现在会在onStop()之前被调用而不是在onPause()之前。这些变化在下表中总结:

Honeycomb之前 Honeycomb之后
Activity是否可以在onPause()之前被杀掉? NO NO
Activity是否可以在onStop()之前被杀掉? YES NO
onSaveInstanceState(Bundle) 保证在...之前被调用 onPause() onStop()

由于Activity生命周期的轻微变化,support library有时候需要根据系统版本选择他的行为。比如,在Honeycomb及以上设备,每次在onSaveInstanceState()之后调用commit()都会抛出一个异常,以便警告开发者已经发生了状态丢失。然而,在每次这种情况抛出异常在Honeycomb之前的设备上就显得太具有限制性了,它们的onSaveInstanceState()调用发生在Activity生命周期中更早的一段时期,并且更容易导致意外的状态丢失。Android团队被迫做出妥协:为了更好地跟老版本兼容,旧设备可能必须要忍受在onPause()和onStop()之间意外的状态丢失。Support library在不同两个版本的行为如下表总结:

Honeycomb之前 Honeycomb之后
commit在onPause()之前 OK OK
commit在onPause() 和onStop()之间 STATE LOSSOK
commit在onStop()之后 EXCEPTION EXCEPTION

怎么避免这个异常?

一旦你懂得了真正发生了什么,避免Activity状态丢失就简单多了。如果你已经在读这篇文章之间就已经解决过这个问题了,希望你能对support library有一个更深的了解,并且知道为什么避免状态丢失对你的程序这么重要。为了方便你通过这篇文章寻找快速的解决方案,这里有一些建议希望你记得在使用FragmentTransactions的时候使用:

在Activity生命周期方法中commit transation的时候一定要小心。

很多应用程序只会在onCreate()或者为了响应用户输入的时候调用一次,所以他们不会遇到任何问题。然而,当你的transation开始冒险在其他的生命周期(比如onActivityResult(),onStart(),onResume() )中commit的时候,事情就可能变得棘手了。比如,你不应该在FragmentActivity#onResume() 方法中commit transation,为了避免有些时候这个方法在Activity的状态恢复之前被调用( 查看文档,了解更多)。如果你的应用程序需要在处理onCreate()之外的生命周期方法中commit transation,在FragmentActivity#onResume() 或者Activity#onPostResume()中调用。这两个方法会被保证在Activity恢复它的状态之后调用,因此会避免可能的状态丢失。

避免是异步调用方法中执行transactions。

这个包括经常被使用的方法比如AsyncTask#onPostExecute() 和LoaderManager.LoaderCallbacks#onLoadFinished() 。在这些方法中执行transactions会有问题,因为他们当这些方法被回调的时候,他们不知道Activity当前的生命周期。比如,考虑下面的事件序列:

一个Activity执行一个AsyncTask
用户按下Home键,导致这个Activity的onSaveInstanceState()和onStop() 方法被回调。
AsyncTask完成然后onPostExecute()被调用,而不知道Activity已经处于stopped状态。
在onPostExectute()方法中的FragmentTransaction被committed,导致一个异常被抛出。

总之,在这些案例中避免异常抛出的最优方法就是避免在异步回调方法中commit transactions。Google工程师似乎同意这个见解。根据在Android Develop group上的这篇文章,Android开发团队认为通过commit FragmentTransactions来让UI产生重大的变化对用户体验十分不友好。如果你的应用程序需要在这些回调方法中执行transaction,那么没有什么简单方法可以保证这些回调不会再onSaveInstanceState()后调用,你可能必须使用commitAllowStateLoss()并且处理可能发生的状态丢失。(详见两篇StackOverFlow文章,文章1文章2)

只使用commitAllowingStateLoss()

作为最后的解决方案。commit()和commitAllowingStateLoss()唯一的区别是后者在状态丢失的时候不会抛出异常。通常你不会想使用这个方法因为它意味着状态丢失可能发生。更好的解决方案当然是修改你的程序以便commit()被保证在activity的状态被保存前调用,因为这样可能会让用户体验更好。除非状态丢失是不可避免的,否则commitAllowingStateLoss()就不应该被使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容