Fragment Transactions & Activity State Loss

原文地址:http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

作者:Alex Lockwood 3013年8月20日

Honeycomb首版发布以来,以下堆栈跟踪和异常消息就一直困扰着StackOverflow

<span style="font-size:18px;">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)</span>

本文将解释为什么和什么时候会出现这个异常,并提出一些建议,以便您的应用程序再也不会因为这个异常崩溃。

为什么会出现这个异常?

在Activity状态保存后,如果你试图提交一个FragmentTransaction,就会出现该异常,从而会造成Activity状态丢失。在我们详细讨论这意味着什么之前,让我们先来看看onSaveInstanceState()被调用之后会发生什么。在我的上一篇文章Binders &Death Recipients中,我提到,在Android运行时环境中,Android应用很难控制自身的命运。Android系统可以在任何时候终止进程以释放内存,作为结果,几乎没有任何警告,后台activity就被杀死了。为了保证这种有时候不稳定的行为不影响到用户,Android框架允许Activity在销毁之前通过调用onSaveInstanceState()来保存状态。当恢复被保存的状态时,不管Activity是否被系统杀死过,用户都会以为是在前台和后台进程中无缝切换。

系统调用onSaveInstanceState()时,会向这个方法传递一个Activity的Bundle对象来保存Activity状态,Activity会把dialog、fragment、view的状态记录在这个Bundle对象中。当方法返回时,系统通过Binder接口,把Bundle对象安全的存储到System Server 进程中。当系统重新创建Activity时,向应用程序发送同一个Bundle对象,用来恢复Activity的旧状态。

那为什么会抛出这个异常呢?这个问题源于一个事实,即这些Bundle对象只是Activity在调用onSaveInstanceState()之后一个时刻的快照,仅此而已。也就是说当你调用onSaveInstanceState()之后再调用FragmentTranceState()#commit()时,这个transaction不会被记录,因为从一开始就没有把它当成Activity状态的一部分。从用户的角度来看,这个transaction会丢失,从而导致意外的UI状态丢失。但是为了保证用户体验,Android系统不惜一切代价避免状态丢失,所以当状态丢失出现时只是简单的抛出一个IllegalStateException异常。

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

如果你遇到过这个异常,你可能已经注意到,不同平台版本之间出现这个异常的时机略有不同。例如,你可能发现旧设备不经常抛出这个异常,或者当你使用support library时,比使用官方框架更容易崩溃。这些微小的不一致让很多人认为support library有错误,不能信任这些support library。但是事实并非如此。

这些微小的不一致源于Honeycomb.Prior到Honeycomb的升级,这次升级对Activity生命周期做了很大的调整。Honeycomb之前,只有Activity暂停后才能被杀死,也就意味着onSaveInstanceState()在紧邻onPause()之前被调用,然而从Honeycomb开始,Activity只有在停止后才能被杀死,也就是说,现在,onSaveInstanceState()会紧邻onStop()之前被调用,而不是紧邻onPause()之前。这些差异如下表所示:

pre-Honeycomb post-Honeycomb
Activities can be killed before onPause()? NO NO
Activities can be killed before onStop()? YES NO
onSaveInstanceState(Bundle) is
guaranteed to be called before...
onPause() onStop()

对生命周期的改动,导致support library有时候需要根据平台版本改变其行为。例如,在Honeycomb及以上设备中,每次调用onSaveInstanceState()之后再调用commit(),都会抛出一个异常来警告开发者发生了状态丢失。但是,对于Honeycomb之前的设备来说,每次都抛出一个异常是过于严格了,这类设备在Activity中更早的调用了onSaveInstanceState(),也更容易产生状态丢失。Android团队被迫做出妥协:为了更好地与旧版本的平台交互,旧版本不得不接受onPause()和onStop()之间产生的意外状态丢失。两个平台的support
library行为总结如下:

pre-Honeycomb post-Honeycomb
commit() before onPause() OK OK
commit() between onPause() and onStop() STATE LOSS OK
commit() after onStop() EXCEPTION EXCEPTION

如何避免这个异常?

一旦你明白真正是怎么回事,避免Activity状态丢失就相当容易了。如果你已经明白了上面所讲的,希望你对support library如何工作以及它为什么对避免状态丢失如此重要也了解一些。或许你在这篇文章中只想找到快速的解决方法,但是,有一些建议,希望你在使用FragmentTransactions的时候能记在脑海里:

  • 在Activity生命周期方法中commit transaction的时候要小心。

大部分应用只会在onCreate()方法首次被调用,和/或响应用户输入时commit transaction,这样不会出现任何问题。但是,当你的transaction进入其他的Activity生命周期,例如,onActivityResult(),onStart(),onResume(),事情就有点棘手了。例如,你不应该在FragmentActivity#onResume() 方法中commit transaction,因为有时候会在Activity状态恢复之前调用onResume()(参考文档)。如果你的应用需要在onCreate()以外的生命周期方法中commit
transaction,把它放在FragmentActivity#onResumeFragments()或者Activity#onPostResume()中。这两个方法都能保证在Activity状态恢复之后被调用,从而避免了状态丢失。(例子:点击打开链接

  • 避免异步回调方法内部执行transaction。

包括常用的AsyncTask#onPostExecute()和LoaderManager.LoaderCallback#onLoadFinished()。在这些方法中执行transaction的问题是,在他们被调用时不知道Activity生命周期的当前状态。例如,考虑下面两个事件的顺序:

  1. Activity执行一个AsyncTask
  2. 用户按下“Home”键,onSaveInstanceState()和onStop()被调用。
  3. AsyncTask执行完毕,onPostExecute()被调用,不知道Activity已经停止。
  4. onPostExecute()中的一个FragmentTransaction被commit,导致异常抛出。

通常,避免这个异常的最好的方法是,避免在异步回调方法中commit transaction。谷歌工程师似乎也同意这种观点。发表在Android Developers group上的这篇文章中,Android团队认为那些因在异步回调方法中commit
FragmentTransaction而导致的UI切换产生了不好的用户体验。如果你的应用需要在回调方法中执行transaction并且没有简单地方法保证紧跟着onSaveInstanceState()调用callback,那么你需要借助commitAllowingStateLoss(),并且处理可能出现的状态丢失(查看StackOverflow上的这两篇文章来获取更多提示,文章一文章二

  • 把commitAllowingStateLoss()放到最后考虑。

调用commit()和commitAllowingStateLoss()的唯一不同点是,如果出现状态丢失,后者不会抛出异常。通常你不会想使用这种方法,因为它意味着状态丢失的可能。当然,更好的办法是在你的应用程序中保证在Activity状态保存之前调用commit(),这样才会有好的用户体验。除非不能避免状态丢失,否则不应该使用commitAllowingStateLoss()

希望这些提示能帮助你解决过去你遇到的关于这个异常的问题,如果仍有疑问,请在StackOverflow上提出问题,在本文的评论中贴出链接,我会查看的 :)

时间: 2024-10-12 17:21:25

Fragment Transactions & Activity State Loss的相关文章

Fragment提交transaction导致state loss异常

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

Fragment Transactions和Activity状态丢失

本文由 伯乐在线 - 独孤昊天 翻译.未经许可,禁止转载!英文出处:androiddesignpatterns.欢迎加入翻译组. 下面的堆栈跟踪和异常代码,自从Honeycomb的初始发行版本就一直使得StackOverflow很迷惑. 1 2 3 4 5 java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState     at android.support.v4.app.Fragm

Fragment Transactions Reference/Animations

I recently found myself looking at Fragment transactions and wondering precisely what each Fragment state meant.  I vaguely had an idea of what added, attached, shown, hidden, detached and removed meant, but not the specific behaviors of each.  I spe

Fragment、Activity 保存状态

Activity 保存状态1. void onCreate(Bundle savedInstanceState) 当Activity被第首次加载时执行.我们新启动一个程序的时候其主窗体的onCreate事件就会被执行.如果Activity被销毁后(onDestroy后),再重新加载进Task时,其onCreate事件也会被重新执行.注意这里的参数 savedInstanceState(Bundle类型是一个键值对集合,大家可以看成是.Net中的Dictionary)是一个很有用的设计,由于前面已

深入分析:Fragment与Activity交互的几种方式(一,使用Handler)

这里我不再详细介绍那写比较常规的方式,例如静态变量,静态方法,持久化,application全局变量,收发广播等等. 首先我们来介绍使用Handler来实现Fragment与Activity 的交互. 第一步,我们需要在Activity中定义一个方法用来设置Handler对象. public void setHandler(Handler handler) { mHandler = handler; } 第二步,在Fragment中的回调函数onAttach()中得到Fragment所在Acti

[转]于Fragment和Activity之间onCreateOptionsMenu的问题

Fragment和Activity一样,可以重写onCreateOptionsMenu方法来设定自己的菜单,其实这两个地方使用onCreateOptionsMenu的目的和效果都是完全一样的,但是由于Fragment是从属于activity的,因此第一次使用onCreateOptionsMenu的时候需要注意以下知识点. 一.在Activity和Fragment中onCreateOptionsMenu的实现是有细微差别的: 在activity中: 1 2 3 4 5 @Override publ

Android中Fragment和Activity之间的互操作代码例子

摘要 本文介绍了Android中一个Activity中有多个Fragment的情况下,Fragment之间如何通过Activity进行互操作. 源代码 源代码地址为:http://download.csdn.net/detail/logicteamleader/8931199 源代码使用ADT编写,ADT版本为2014,Android版本为android-22. 技术要点 1.在Activity中的多个Fragment之间要互操作,一定要通过此Activity,不能直接通信: 2.在Activi

实现Android 动态加载APK(Fragment or Activity实现)

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38565345 最近由于项目太大了,导致编译通不过(Android对一个应用中的方法个数貌似有限制),所以一直琢磨着能否将某些模块的APK不用安装,动态加载,通过在网上查找资料和网友的帮助,终于实现了APK的动态加载,网络上介绍APK动态加载的文章非常多,但是我觉得写得非常好的就是这位大牛的,我基本上就是使用他的这种方案,然后加入了自己的元素.这位大牛是通过Activity实现的,我稍作修改

深入分析:Fragment与Activity交互的几种方式(三,使用接口)

第一步:我们需要在Fragment中定一个接口,并确保我们的容器Activity实现了此接口: public interface onTestListener { public void onTest(String str); } @Override public void onAttach(Activity activity) { super.onAttach(activity); // 这个方法是用来确认当前的Activity容器是否已经继承了该接口,如果没有将抛出异常 try { mCal