Fragment的事务操作&Actvity的状态丢失
Fragment Transactions & Activity 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)`
这篇文章就是来解释这个异常发生的原因
和异常抛出的时机
,并且给出了一些有益的建议来避免这个异常的发生
为什么会抛出这个异常?
这个异常的抛出是由于你准备在actvity的状态已经被保存后来做一次FragmentTransaction
的commit,这将会导致Actvity state loss
的现象的出现。
在我们深入讨论这个现象之前,我们先来看一下在onSaveInstanceState()
方法调用后发生了什么。正如我在上一篇博文中提到的http://www.androiddesignpatterns.com/2013/08/binders-death-recipients.html,在android runtime 期间 android应用自己几乎不能控制自己,android系统有权在任何时候释放内存,因此后台的actvity也会被毫无征兆的kill掉。为了确保这种无法估计的行为对用户是无感知的,framework给予每个activity在觉察自己可能(back键除外)会被销毁时调用onSaveInstanceState()
来保存自己的状态,用户在前后台切换actvity时,保存的状态数据将会被恢复时,而不会觉察到这个activity是否已经是被系统kill的,在用户看来这是“无缝”切换的。
当framework调用onSaveInstanceState()
,它传递一个Bundle对象给actvity来保存它的diaglog、fragments、view的信息
。当方法回调时,系统通过Binder接口传递这个Bundle对象到System Sever,在这里Bundle对象将被安全的存储。然后系统之后重建actvity时再将刚才的Bundle传递给应用,这时actvity得到之前保存的状态
铺垫只是解释完之后下面将详细解释为什么会抛出这个异常。这个问题源于这样一个事实,传递的Bundle
对象代表着activity在调用onSaveInstanceState()
这一时刻的肖像刻画,这就意味着,在onSaveInstanceState()
之后调用FragmentTransaction#commit()
这个transaction将不会被记录,因为它一开始就没有被记录在Activity的状态中。从用户看来,actvity切换恢复时这个transaction体现为丢失的,这将导致Activity的UI状态丢失。为了保护用户的体验,Android不惜一切代价避免状态丢失,出现时就抛出一个异常来提醒开发者。
何时抛出这个异常
如果你之前遇到过这个异常,你可能会注意到,这个异常的抛出随着平台不同的而变得有点不一致。举例来说,你可能会发现老旧的机器抛出这个异常更少些,或者使用support library比官方的framework更容易出现这个异常。这些轻微的矛盾导致了一些诸如“support library 有bug,不可信”的论调,然而这通常都不是真的。
这些轻微矛盾的出现是源于在Honeycomb
中Activity生命周期的变化,在Honeycomb
之前,actvity在调用OnPause方法之后才能被killed的,这就意味着onSaveInstanceState()
需要在在OnPause
之前调用。而在Honeycomb
版本时,actvity只有在onStop之后才能被killed,这也就意味着onSaveInstanceState()
需要在在OnStop
之前调用而不是以前版本中的OnPause
之前调用
Activity生命周期的微小改变,将使得support library
有时需要基于平台来改变它的一些行为,举例来说,在Honeycomb
及其以上的设备中,每次在onSaveInstanceState()
之后commit都会抛出这个异常,然而在Honeycomb
之前的设备中异常就会出现少一些。android团队被迫做出让步:为了老版本的内在更好地兼容,老的设备不得不忍受在 onPause() and onStop()之间的状态丢失support library的行为在不同平台之间总结如下
如何避免这个异常
当你明白到底真正发生了什么你就会发现activity避免状态丢失是多么简单。如果你在这篇博文中做到这一步,希望你能明白整个
support library是如何工作的,并且为什么避免状态丢失是如此重要。为了方便你在这篇博文寻找一个快速修复的方法,这里有一些建议需要牢牢记住,当你在应用中使用FragmentTransactions
的时候
- 在Actvity的生命周期中commit transactions需要小心谨慎
大多数应用只会在在onCreate中第一次commit transaction,这当然不会遇到这种问题,然而当你的transaction企图在Activity生命周期其他方法,诸如onActivityResult(), onStart(), and onResume()中commit时,这时事情就变得棘手了。举例来说,你不该在 FragmentActivity#onResume()中commit transaction,这是由于有一些情况,这个方法有时候会在activity状态保存前调用(参考developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume())。因此如果你的应用需要在Activity生命周期onCreate()之外commit transaction,只在FragmentActivity#onResumeFragments()
orActivity#onPostResume()
方法中去commit,这两个方法能够保证Activty状态保存之后再调用,这样就避免了状态丢失(参考http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult)
- 避免在异步中执行 commit transactions
这包括了常用的一些方法,诸如:AsyncTask#onPostExecute() and LoaderManager.LoaderCallbacks#onLoadFinished()等。由于这些方法根本不知道在当前Activity哪一个生命周期的去调用了他们,因而在其中commit transaction就会出问题。比如
An activity executes an AsyncTask.
The user presses the “Home” key, causing the activity’s onSaveInstanceState() and onStop() methods to be called.
The AsyncTask completes and onPostExecute() is called, unaware that the Activity has since been stopped.
A FragmentTransaction is committed inside the onPostExecute() method, causing an exception to be thrown.
总而言之就是要在异步回调方法中避免去commit transaction来避免这个异常的发生。谷歌工程师看起来也是同意这种观念的。通过android gruop的这个文章(https://groups.google.com/d/msg/android-developers/dXZZjhRjkMk/QybqCW5ukDwJ) ,谷歌android team 认为通过异步commit transaction 带来的UI变化并不利于用户体验。如果你的应用不得不在异步中提交,你将不得不使用commitAllowingStateLoss()
并且处理好状态可能丢失的发生的情形(http://stackoverflow.com/questions/7992496/how-to-handle-asynctask-onpostexecute-when-paused-to-avoid-illegalstateexception和http://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused)
- 将 commitAllowingStateLoss()作为最后使用的手段
commit和commitAllowingStateLoss唯一的区别就是后者在状态丢失时候不会抛出异常,通常不会使用这个方法,因为这将意味着状态的可能丢失。最好的方式就是写好你的应用,这样transaction确保在activity状态保存之后commit,这样用户体验会更好。除非状态丢失不可避免了,否则不要使用commitAllowingStateLoss()
Fragment的事务操作&Actvity的状态丢失的更多相关文章
- 应用 TransactionScope 报:此操作对该状态的事务无效 的错误
如果在事务过程跨了数据库服务器(即使在同一台服务器上,两个不同的数据库实例也算跨数据库服务器),而使用 TransactionScope 却报:此操作对该状态的事务无效 的错误 是因为没有启用每台服务 ...
- Fragment Transactions和Activity状态丢失
本文由 伯乐在线 - 独孤昊天 翻译.未经许可,禁止转载!英文出处:androiddesignpatterns.欢迎加入翻译组. 下面的堆栈跟踪和异常代码,自从Honeycomb的初始发行版本就一直使 ...
- (转)Spring中的事务操作
http://blog.csdn.net/yerenyuan_pku/article/details/70024364 事务的回顾 什么是事务 事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么 ...
- MySQL学习-MySQL内置功能_事务操作
1.事务详解 1.1 事务的概念 MySQL 事务主要用于处理操作量大,复杂度高的数据.比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等 ...
- spring学习(八)事务操作
一.事务的概念: 事务是并发控制的单位,一系列操作组成的工作单元,该工作单元内的操作是不可分割的,也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做,所有操作必须成功完成,否则 ...
- android 自定义adapter和线程结合 + ListView中按钮滑动后状态丢失解决办法
adapter+线程 1.很多时候自定义adapter的数据都是来源于服务器的,所以在获取服务器的时候就需要异步获取,这里就需要开线程了(线程池)去获取服务器的数据了.但这样有的时候adapter的中 ...
- Oracle 数据库基本操作——实用手册、表操作、事务操作、序列
目录: 0. 参考链接与参考手册1. oracle 实用(常用操作)指令2. 数据库基本操作语法 a) 表操作 1)创建表 2)更新表 3)删除表 4)查询 b) 事务操作 c) 序列操作 1)创建序 ...
- 使用JDBC进行数据库的事务操作(1)
本篇讲述数据库中非常重要的事务概念和如何使用MySQL命令行窗口来进行数据库的事务操作.下一篇会讲述如何使用JDBC进行数据库的事务操作. 事务是指数据库中的一组逻辑操作,这个操作的特点就是在该组逻辑 ...
- MongoDB模拟多文档事务操作
Mongodb不支持多文档原子性操作,因此依据两阶段提交协议(Two Phase Commits protocol)来模拟事务. 以两个银行账户之间的转账行为为例,来说明如何实现多文档间的事务操作. ...
随机推荐
- hdu2795 线段树 贴广告
Billboard Time Limit: 20000/8000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total ...
- [Apio2009][bzoj1179]Atm
题意:一个n个点m条单向边的图,每个点有权值,给定出发点和p个可以停止的点,你可以随便走一条路径从出发点走到一个可以停止的点,但是每个点的点权只能计算一次,求能得到的最大权值. n,m<=500 ...
- bzoj2127
2127: happiness Time Limit: 51 Sec Memory Limit: 259 MBSubmit: 2492 Solved: 1205[Submit][Status][D ...
- xx学院学员评优评奖管理系统
[勤拂拭软件,软件开发,毕业设计,程序作业,论文写作指导:q-[1215714557] 加好友请注明:勤拂拭)] 之前帮助一个军校学生做的一个评优评奖管理系统,该系统主要用于学校学生评优评先使用. ...
- 在浏览器中运行Keras模型,并支持GPU
Keras.js 推荐一下网页上的 demo https://transcranial.github.io/keras-js/#/ 加载的比较慢,但是识别的非常快. Run Keras models ...
- box-sizing position
box-sizing 属性 用于更改用于计算元素宽度和高度的默认的 CSS 盒子模型.可以使用此属性来模拟不正确支持CSS盒子模型规范的浏览器的行为. /* 关键字 值 */ box-sizing: ...
- idea,mybatis读取配置文件报错:Could not find resource configuration.xml
在pom.xml中,把xml文件加入编译,成功解决问题. <build> <resources> <resource> <directory>src/m ...
- PWA初体验
一.前言 现在市面上的Native APP成千上万个,各种应用商店里面的APP琳琅满目.原生的APP下载到手机上之后,用户就可以获取一个方便的入口,体验上也十分顺畅.但是再好的事物难免有点缺点: 1 ...
- 阿里云部署mongdb(CentOS)
配置包管理系统 (yum). Xshell登录Linux查看操作系统版本信息 lsb release -a 可以在官网选择对应的版本 :官网的安装指导文档http://docs.mongodb.org ...
- MySQL数据类型DECIMAL用法
MySQL DECIMAL数据类型用于在数据库中存储精确的数值.我们经常将DECIMAL数据类型用于保留准确精确度的列,例如会计系统中的货币数据. 要定义数据类型为DECIMAL的列,请使用以下语法: ...