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.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)

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

这个异常为什么会抛出?

这个异常抛出的原因是因为你尝试着在Activity的状态已经保存后commit一个FragmentTransaction ,导致了一个现象叫做Activity state loss。在我们深入细节之前,让我们先看看在onSaveInstanceState()调用后发生了什么。在我的前一篇 Binders & Death Recipients有讨论到,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 LOSS OK
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恢复它的状态之后调用,因此会避免可能的状态丢失。(一个关于如何去做的例子,可以查看我在StackOverFlow上的回答。这个回答设计怎么正确地响应Activity#onActivityResult()方法,然后commit FragmentTransactions)
  • 避免是异步调用方法中执行transactions 。这个包括经常被使用的方法比如AsyncTask#onPostExecute() 和 LoaderManager.LoaderCallbacks#onLoadFinished() 。在这些方法中执行transactions会有问题,因为他们当这些方法被回调的时候,他们不知道Activity当前的生命周期。比如,考虑下面的事件序列:
    1. 一个Activity执行一个AsyncTask
    2. 用户按下Home键,导致这个Activity的onSaveInstanceState()和onStop() 方法被回调。
    3. AsyncTask完成然后onPostExecute()被调用,而不知道Activity已经处于stopped状态。
    4. 在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()就不应该被使用。

希望这些建议能够帮助你解决以往因为这个异常而引起的问题。如果你还有问题,在StackOverflow上发布问题,然后将链接发表在评论区以便我能够看到。

译文链接Fragment transaction commit state loss

*:first-child {
margin-top: 0 !important;
}

body>*:last-child {
margin-bottom: 0 !important;
}

/* BLOCKS
=============================================================================*/

p, blockquote, ul, ol, dl, table, pre {
margin: 15px 0;
}

/* HEADERS
=============================================================================*/

h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}

h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}

h1 {
font-size: 28px;
color: #000;
}

h2 {
font-size: 24px;
border-bottom: 1px solid #ccc;
color: #000;
}

h3 {
font-size: 18px;
}

h4 {
font-size: 16px;
}

h5 {
font-size: 14px;
}

h6 {
color: #777;
font-size: 14px;
}

body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
margin-top: 0;
padding-top: 0;
}

a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}

h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
margin-top: 10px;
}

/* LINKS
=============================================================================*/

a {
color: #4183C4;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}

/* LISTS
=============================================================================*/

ul, ol {
padding-left: 30px;
}

ul li > :first-child,
ol li > :first-child,
ul li ul:first-of-type,
ol li ol:first-of-type,
ul li ol:first-of-type,
ol li ul:first-of-type {
margin-top: 0px;
}

ul ul, ul ol, ol ol, ol ul {
margin-bottom: 0;
}

dl {
padding: 0;
}

dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}

dl dt:first-child {
padding: 0;
}

dl dt>:first-child {
margin-top: 0px;
}

dl dt>:last-child {
margin-bottom: 0px;
}

dl dd {
margin: 0 0 15px;
padding: 0 15px;
}

dl dd>:first-child {
margin-top: 0px;
}

dl dd>:last-child {
margin-bottom: 0px;
}

/* CODE
=============================================================================*/

pre, code, tt {
font-size: 12px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}

code, tt {
margin: 0 0px;
padding: 0px 0px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}

pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}

pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}

pre code, pre tt {
background-color: transparent;
border: none;
}

kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #DDDDDD;
background-image: linear-gradient(#F1F1F1, #DDDDDD);
background-repeat: repeat-x;
border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
border-image: none;
border-radius: 2px 2px 2px 2px;
border-style: solid;
border-width: 1px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
line-height: 10px;
padding: 1px 4px;
}

/* QUOTES
=============================================================================*/

blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}

blockquote>:first-child {
margin-top: 0px;
}

blockquote>:last-child {
margin-bottom: 0px;
}

/* HORIZONTAL RULES
=============================================================================*/

hr {
clear: both;
margin: 15px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
border-bottom: 4px solid #ddd;
padding: 0;
}

/* TABLES
=============================================================================*/

table th {
font-weight: bold;
}

table th, table td {
border: 1px solid #ccc;
padding: 6px 13px;
}

table tr {
border-top: 1px solid #ccc;
background-color: #fff;
}

table tr:nth-child(2n) {
background-color: #f8f8f8;
}

/* IMAGES
=============================================================================*/

img {
max-width: 100%
}

func {
font-size: 0.95em;
color: #cc0000;
font-family: Consolas, Menlo, Monaco, ‘Lucida Console‘, ‘Liberation Mono‘, ‘DejaVu Sans Mono‘, ‘Courier New‘, monospace;
line-height: 1.3em;
}
-->

时间: 2024-08-08 05:20:22

Fragment提交transaction导致state loss异常的相关文章

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

Android的debug.keystore拒绝访问导致的生成异常及解决方案

构建Android应用程序的时候输出异常:[apkbuilder] keytool 错误: java.io.FileNotFoundException: C:\Users\my\.android\debug.keystore(拒绝访问.)导致BUILD FAILED. 异常原因: Android要求所有的应用程序必须有签名,否则就不会安装该程序.而在我们开发过程中,默认生成和使用debug.keystore(所以平时根本不会注意有这么个玩意),debug.keystore默认有效期为一年,换句话

YS VTM模块存在格式化字符串漏洞,可导致VTM进程异常退出【高危】

YS VTM模块存在格式化字符串漏洞,可导致VTM进程异常退出[高危] 问题描述:          YS VTM模块开放对外监听端口(8554和8664),此次使用sulley fuzzing框架对监听在8664端口的私有二进制协议进行测试,以检测可能发生的各种问题.在该协议中,客户端会向8664端口发送一个二进制+文本格式的报文,对该报文格式的各个字段进行fuzzing,发现当向服务端的VTM进程传入格式化字符串时会崩溃并退出. 测试步骤: 1.  分别在客户机和服务器安装sulley fu

采用抽象类优化Fragment提交代码

记住一直framgent,总是framgent是咱们android开发的一个原则. 一.使用framgnet的通用用法 先看下面的这段代码,一段使用framgent的通用用法.由于该布局并没有指定一个特定的fragment,因此只要有activity托管fragment就可以使用这个通用布局. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="htt

oracle所在磁盘空间不足导致了数据库异常

oracle所在磁盘空间不足导致了数据库异常.需要减小数据文件的大小来解决. 1.检查数据文件的名称和编号 select file#,name from v$datafile; 2.看哪个数据文件所占的空间最大,根据该数据文件的编号查看该数据文件的最大数据块数 select max(block_id) from dba_extents where file_id=8; 查询结果 3.计算该表空间的实际占用空间.(不是物理文件的大小) 查看每个数据块的大小 show parameter db_bl

SpringBoot使用devtools导致的类型转换异常

遇到的问题:SpringBoot项目中的热部署引发的血的教训,报错代码位置: 1 XStream xStream1 = new XStream(); 2 xStream1.autodetectAnnotations(true); 3 xStream1.alias("InterBOSS", InterBossHeader.class); 4 InterBossHeader resp = (InterBossHeader) xStream1.fromXML(response); 项目中的p

Hibernate 中 load() 方法导致的 noSession 异常

之所以要写这个,是因为最近碰到了一个延迟加载的 load() 导致出现 noSession 的异常. 解决这个问题需要用到一个本地线程的对象,也就是 ThreadLocal 类,之前写过关于这个对象,可以看这个博客[本地线程 ThreadLocal 类] 我在数据层中封装了一个 load() 方法,根据用户 Id 获取用户对象: public UserModel get(Long uuid) { return this.getHibernateTemplate().load(UserModel.

【原】Maven解决jar冲突调试步骤:第三方组件引用不符合要求的javassit导致的相关异常

[环境参数]开发框架:Spring + MyBatis + SpringMVC + KettleJDK版本:1.8.0_91javassist依赖版本:javassit-3.12.1.GA [障碍再现]在Kettle工具初始化时,抛出如下异常:java.io.IOException: invalid constant type: 15 at javassist.bytecode.ConstPool.readOne(ConstPool.java:1090) at javassist.bytecod

MySQL从库的列类型不一致导致的复制异常问题

官方文档:https://dev.mysql.com/doc/refman/5.6/en/replication-features-differing-tables.html slave_type_conversions  这个参数在mysql5.5.3 引入,目的是启用row 格式的bin-log 的时候,如果主从的column 的数据类型不一致,会导致复制失败,mysql5.5.3 之后支持,主库是int 从库是bigint 这种类型的复制, 这个参数的意义就是控制些类型转换容错性. 如果从