Fragment Transactions & Activity State Loss

本文翻译自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-illegalstateexceptionhttp://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused

  • 将 commitAllowingStateLoss()作为最后使用的手段

commit和commitAllowingStateLoss唯一的区别就是后者在状态丢失时候不会抛出异常,通常不会使用这个方法,因为这将意味着状态的可能丢失。最好的方式就是写好你的应用,这样transaction确保在activity状态保存之后commit,这样用户体验会更好。除非状态丢失不可避免了,否则不要使用commitAllowingStateLoss()

Fragment的事务操作&Actvity的状态丢失的更多相关文章

  1. 应用 TransactionScope 报:此操作对该状态的事务无效 的错误

    如果在事务过程跨了数据库服务器(即使在同一台服务器上,两个不同的数据库实例也算跨数据库服务器),而使用 TransactionScope 却报:此操作对该状态的事务无效 的错误 是因为没有启用每台服务 ...

  2. Fragment Transactions和Activity状态丢失

    本文由 伯乐在线 - 独孤昊天 翻译.未经许可,禁止转载!英文出处:androiddesignpatterns.欢迎加入翻译组. 下面的堆栈跟踪和异常代码,自从Honeycomb的初始发行版本就一直使 ...

  3. (转)Spring中的事务操作

    http://blog.csdn.net/yerenyuan_pku/article/details/70024364 事务的回顾 什么是事务 事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么 ...

  4. MySQL学习-MySQL内置功能_事务操作

    1.事务详解 1.1 事务的概念 MySQL 事务主要用于处理操作量大,复杂度高的数据.比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等 ...

  5. spring学习(八)事务操作

    一.事务的概念: 事务是并发控制的单位,一系列操作组成的工作单元,该工作单元内的操作是不可分割的,也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做,所有操作必须成功完成,否则 ...

  6. android 自定义adapter和线程结合 + ListView中按钮滑动后状态丢失解决办法

    adapter+线程 1.很多时候自定义adapter的数据都是来源于服务器的,所以在获取服务器的时候就需要异步获取,这里就需要开线程了(线程池)去获取服务器的数据了.但这样有的时候adapter的中 ...

  7. Oracle 数据库基本操作——实用手册、表操作、事务操作、序列

    目录: 0. 参考链接与参考手册1. oracle 实用(常用操作)指令2. 数据库基本操作语法 a) 表操作 1)创建表 2)更新表 3)删除表 4)查询 b) 事务操作 c) 序列操作 1)创建序 ...

  8. 使用JDBC进行数据库的事务操作(1)

    本篇讲述数据库中非常重要的事务概念和如何使用MySQL命令行窗口来进行数据库的事务操作.下一篇会讲述如何使用JDBC进行数据库的事务操作. 事务是指数据库中的一组逻辑操作,这个操作的特点就是在该组逻辑 ...

  9. MongoDB模拟多文档事务操作

    Mongodb不支持多文档原子性操作,因此依据两阶段提交协议(Two Phase Commits protocol)来模拟事务. 以两个银行账户之间的转账行为为例,来说明如何实现多文档间的事务操作. ...

随机推荐

  1. 服务器使用nginx做代理,通过HttpServletRequest获取请求用户真实IP地址

    首先,在nginx配置中添加如下配置 server { listen ; server_name www.wenki.info; #要访问的域名 charset utf8; location / { ...

  2. MySQL插件实现浅析——插件的调用

    一. MySQL中的动态插件 最初想到这个问题是在学习mysql半同步复制相关问题的时候,为何在mysql运行时install半同步插件并开启后就能起到作用,他是如何让事务停下来等待的.安装插件的时候 ...

  3. 缓冲区(buffer)与缓存(cache)

    下面介绍缓冲区的知识. 一.什么是缓冲区 缓冲区(buffer),它是内存空间的一部分.也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区, ...

  4. SecureFX连接Linux后文件夹中文乱码问题解决(转)

    在使用SecureFX 连接Linux 时,发现文件夹显示乱码,一直尝试各种配置,现将方法整理一下!供大家参考! 首先在选项中设置字符编码为UTF-8 然后在全局选项中找到Securefx的配置文件 ...

  5. Timestamp转Calendar

    Timestamp scheduleTime = r.getTimestamp("time_recv"); Calendar calendarScheduleTime = Cale ...

  6. pupeteer初体验

    官方文档: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions puppet ...

  7. linux系统运维面试题简答

    1.     简述常用高可用技术 解答: Keepalived:Keepalived是一个保证集群高可用的服务软件,用来防止单点故障,使用VRRP协议实现.在master和backup之间通过mast ...

  8. 从头开始搭建一个VSCode+NetCore的项目

    看这个前,先要对VS开发C#有所了解 获取作案工具 NetCore SDK https://www.microsoft.com/net/learn/get-started/windows 安装 建立工 ...

  9. Nginx之(三)Nginx配置

    一个简单的配置文件如下: #定义Nginx运行的用户及用户组 user userName userGroupName; #工作进程数目,根据硬件调整,通常等于CPU数量或者2倍于CPU worker_ ...

  10. 实战 PureMVC

    最近看PureMVC,在IBM开发者社区发现此文,对PureMVC的讲解清晰简洁,看了可快速入门.另外,<腾讯桌球>游戏的开发者吴秦,也曾进一步剖析PureMVC,可结合看加深理解. 引言 ...