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. bzoj3156防御准备 斜率优化dp

    3156: 防御准备 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 2279  Solved: 959[Submit][Status][Discuss ...

  2. [APIO2011]

    来自FallDream的博客,未经允许,请勿转载,谢谢. ------------------------------------------------------ A.[Apio2011]方格染色 ...

  3. Python中内置函数的介绍

    内置函数的功能介绍 常用内置函数如下: 1.abs() 绝对值 格式:abs(x) 例如:print(abs(-18)) >>> 18 返回值:number #该函数主要用于数值类的 ...

  4. java随机生成字符串和校验

    首先定义字符串 String a = "0123456789"; // 数字 String b = "abcdefghijklmnopqrstuvwxyz"; ...

  5. Linux学习之CentOS(二)--初识linux的一些常用命令(基础命令)

    初次学习linux系统,首先也得会一些linux的基本命令.至少要先学会开启和关闭系统吧!我称为 基础命令! linux命令是对Linux系统进行管理的命令.对于Linux系统来说,无论是中央处理器. ...

  6. 好用的jquery.animateNumber.js数字动画插件

    在做公司的运营报告页面时,有一个数字累计增加的动画效果,一开始,毫无头绪,不知如何下手,于是上网查资料,发现大多都是用的插件来实现的,那么今天,我也来用插件jquery.animateNumber.j ...

  7. 阿里架构师带你深入浅出jvm

    本文跟大家聊聊JVM的内部结构,从组件中的多线程处理,JVM系统线程,局部变量数组等方面进行解析 JVM JVM = 类加载器(classloader) + 执行引擎(execution engine ...

  8. 记录 Python3 安装 Scrapy 遇到的问题

    开发环境:Windows 10 + Python 3 使用 pip 去安装 Scrapy,  pip install scrapy , 报了一个错误. 原因:加 --user 的作用是显式指定安装在用 ...

  9. 用CSS让DIV上下左右居中的方法

    转载自喜欢JS的无名小站 例如 一个父div(w:100%;h:400px)中有一个子div(w:100px;100px;).让其上下左右居中. 方法一(varticle-align) 理念 利用表格 ...

  10. ACM 今年暑假不AC

    "今年暑假不AC?" "是的." "那你干什么呢?" "看世界杯呀,笨蛋!" "@#$%^&*%... ...