FragmentTransaction&Activity的状态

我的翻译的国外大牛博客 原文连接

自从Android Honeycomb发布以来,下面的异常信息和trace已经在StackOverflow提出了不少了:html

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

这篇博文会解释这个异常信息为何会产生,何时会被抛出。而且经过提供几个建议确保你的App不再会所以而挂掉。java

为何会产生这个异常

当Activity状态(state)已经被保存以后,而你又试图提交一个Fragment事务,系统就会抛出这个异常,直接致使的现象就是Activity的状态丢失了。在咱们深刻问题以前,让咱们先看一下当onSaveInstanceState()被调用时系统作了什么。就像我之前博文Binders & Death Recipients里所说的,Android应用程序很难控制本身的命运。Android有能力在任什么时候候干掉进程去释放内存,而且后台活动可能毫无预兆地被干掉。为了确保用户有时的意外行为,系统框架经过在Actvitiy被干掉以前调用onSaveInstanceState()方法去保存Activity的状态。当保存的状态呗还原后,用户可以在切换前台和后台活动时感觉到无缝切换,而不用考虑活动是否已经被系统干掉了。android

当系统调用onSaveInstanceState()方法时,系统传递了一个Bundle对象好让Activity可以保存它的状态,之中Activity保存的状态包括对话框、Fragment和各类视图等。当这个方法返回后,系统会序列化Bundle对象经过Binder接口安全的存放在系统进程中。当系统决定从新建立这个Activity后,系统会发送一样的Bundle到这个应用,被用来还原Acitivity的老的状态。安全

那么为何会抛出这个异常呢?这个问题的实质就是当Activity调用onSaveInstanceState()方法后,Bundle对象保存了这个Activity的“快照”。这就意味着当你在onSaveInstanceState()被调用以后调用的FragmentTransaction#commit(),由于Activity已经保存了当前的状态而且不会记录这个事务,这个事务的提交就不会被记住。从用户的角度来看,这个事务会被丢掉,致使UI的状态丢失。为了保证用户体验,Android不惜一切避免状态丢失,当它发生的时候,就直接爆出一个IllegalStateException异常。app

何时会抛出这个异常

若是你之前碰见过这个异常,或许你已经注意到不一样版本平台异常抛出的时间略有不一样。例如,你可能发现老设备更少地抛出这个异常,或者你的应用更可能crash在使用support库而不是官方系统库。这就致使了不少人控诉support库有bug而且不可信。事实上,这些控诉都是不正确的。框架

这些不一致的现象的缘由是由于Honeycomb版本对Activity的生命周期作了重大的修改。在Honeycomb以前,Activity在被暂停以前是不可被杀的,这意味着onSaveInstanceState()是在onPause()以前调用的。自从Honeycomb版本,Activity只能在中止之后是可被杀的,意味着onSaveInstanceState()会在onStop()以前调用,而不是在onPause()以前调用。总结以下:异步


Honeycomb以前 Homeycomb以后
Activity能在onPause()以前被干掉 NO NO
Activity能在onStop()以前被干掉 YES NO
onSaveInstanceState(Bundle)一般在哪一个方法以前调用? onPause() onStop()

由于Activity生命周期的改变,support库有时须要根据平台版本的不一样而改变它的行为。例如,设备运行在Honeycomb或以上的系统,每次在onSaveInstanceState()后调用commit()方法,系统都会抛出异常去警告开发者发生了状态丢失。然而,每次都抛出一个异常是过于严格的在设备运行Honeycomb之前的系统,它们的onSaveInstanceState()在Activity生命周期中被更早的调而且更容易致使状态丢失。Android团队不得不作出了妥协:在了在老系统上拥有更好的交互操做,在onPause()onStop()之间,老设备只能承受状态丢失。Support库在不一样版本上不一样的行为可总结为:async


Honeycomb以前 Homeycomb以后
commit()onPause()以前 OK OK
commit()onPause()onStop()之间 STATE_LOSS OK
commit()onStop()以后 EXCEPTION EXCEPTION

如何避免这个异常?

一旦你理解了这个异常的实质状况,避免这个异常就变的很是简单了。若是你已经作到了这一步,但愿你能理解Support是怎样工做的,而且为何在应用中避免状态丢失是若是的重要。若是你只是在查找快速解决方法,下面是一些关于使用FragmentTransaction的建议,在往后的工做中牢记在心:post

  • 在Activity生命周期方法提交事务要格外当心。绝大多数的应用在onCreate()方法的时候或者用户操做的时候就提交了事务,因此不会出现问题。可是,当你在Activity其余生命周期方法中调用事务,如onActivityResult()onStart()onResume()方法等,事情就变的不那么美好了。例如,你不该该在FragmentActivity#onResume()方法中提交事务,由于有时候这个方法会在Activity还原状态以前被调用(详情见文档)。若是你的应用真的须要在Activity生命周期非onCreate()方法中提交事务,请在FragmentActivity#onResumeFragments()Activity#onPostResume()方法中提交。这两个方法会在Activity还原状态以后被调用,所以能够避免状态丢失(一个例子关于怎么使用,请看我对[这个问题的的答案](http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult),里面提供了一些关于在Activity#onActivityResult()方式中提交FragmentTransaction的想法。)。this

  • 在异步回调方法中避免调用事务。这里一般包括了像AsyncTask#onPostExceute()LoaderManager.LoaderCallback#onLoadFinished()方法。在这里调用事务的问题在于当这些方法被调用时他们并不知道当前Activity生命周期状态。例如,考虑一下的步骤:

    1. 一个Activity启动了一个AsyncTask

    2. 用户按下了Home键,致使这个Activity的onSaveInstanceState()onStop()方法被调用。

    3. 这个AsyncTask完成了而且调用了onPostExecute()方法,而没有意识到这个Activity已经被Stopped。

    4. onPostExecute()方法中提交了一个FragmentTransaction,而致使抛出了一个异常。

    一般,避免这个异常最好的方法就是不要在全部的异步回调方法中提交事务。Google工程师好像也坚决这个信念。根据在Google Group上的关于Adnroid Developer的这篇博文,Android团队认为在异步回调方法中提交FragmentTransaction并转变UI是有损用户体验的。若是你应用须要在异步回调方法中调用事务,而且也没有简单的方法去确保回调方法不会在onSaveInstanceState()方法后调用,你只能用最不推荐的方法commitAllowingStateLoss(),你还要处理可能出现的状态丢失。(想要了解更多的这部份内容,请参考这个还有这个

  • 最后再考虑使用commitAllowingStateLoss()方法。调用commit()commitAllowingStateLoss()惟一的不一样就是,当状态丢失发生后,后者不会抛出异常。通常你不想使用这个方法是由于它意味着状态丢失会有可能发生。固然,更好的解决方法就是让你应用必定要在Activity保存状态以前调用commit()方法,这就是更好的用户体验。出发状态丢失不能被避免,不然commitAllowingStateLoss()不该该被使用。

但愿这些tips可以帮助你解决之前遇到的异常。若是你仍然还有问题,在StackOverflow上提一个问题并在评论中留下连接,我会去看一看的。:)

相关文章
相关标签/搜索