Fragment提交transaction导致state loss异常-阿里云开发者社区

开发者社区> shy丶gril> 正文

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之后
commitonPause()之前 OK OK
commitonPause() 和onStop()之间 STATE LOSS OK
commitonStop()之后 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

 


作者:kissazi2 
出处:http://www.cnblogs.com/kissazi2/ 
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载:http://www.cnblogs.com/kissazi2/p/4181093.html

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
异常信息: ResultSet can not re-read row data for column 2
解决方法:下面的解决方案是网上高人说的:                    (1)如果采用jdbc-odbc驱动,那么就必须按照查询顺序来一次读取(不论有没有image或text类型)           (2)如果采用微软提供的ms sql server jdbc driver,...
549 0
OSS异常流量排查及防护
OSS适用于存储各类型的静态资源,较多的用户将静态资源存储在OSS上。当OSS产生大流量的异常流量要如何进行定位分析,OSS恶意访问可能是恶意referer盗链使用或者恶意IP恶意请求访问OSS资源导致的,本文对上述内容进行了介绍;
5524 0
Java异常--其他概念—throw、throws、Exception、RuntimeException、断言
<h1>1、throws关键字</h1> <div> <img src="http://img.blog.csdn.net/20131009151636140" alt=""><br> </div> <div> <span style="white-space:pre"></span>假设定义一个除法的方法div(),对于除法来说:有可能出现异常,有可能不出现异常。所以对于这样的方法来
2085 0
hive 异常 (Attempt to do update or delete on table terminal that does not use an )
    hive 异常 (Attempt to do update or delete on table terminal that does not use an )   hive > delete from terminal where 1=1 ;  出现以下    ...
1418 0
关于异常Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
原文:关于异常Microsoft.CSharp.RuntimeBinder.RuntimeBinderException 关于Microsoft.CSharp.RuntimeBinder.RuntimeBinderException的异常一般来自于两种, 第一种: Predefined type 'Microsoft.CSharp.RuntimeBinder.Binder' is not defined or imported 解决它的办法是,直接在项目引用中添加 Micorsoft.Csharp 就可以了。
1394 0
在 System.Data.OleDb.OleDbException 中第一次偶然出现的“System.Data.dll”类型的异常
C#使用OleDB操作ACCESS插入数据时提示: 在 System.Data.OleDb.OleDbException 中第一次偶然出现的“System.Data.dll”类型的异常 这个错误是在执行: int rlt = updateCmd.ExecuteNonQuery(); 时产生的,但这个信息是在控制台中输出的,而抛出的异常信息通常是:标准表达式中数据类型不匹配。
869 0
SpringBoot-08-之统一化json输出与自定义异常捕获
为及时了解异常,以及避免返回的json格式不统,将每次请求的结果都返还一样的形式。 此处统一为:{"code":响应代号码,"msg":信息,"data":数据} 具体效果如下: result--format.
1287 0
+关注
1878
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载