转自:http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

The following stack trace and exception message has plagued StackOverflow ever since Honeycomb's initial release:

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)

This post will explain why and when this exception is thrown, and will conclude with several suggestions that will help ensure it never crashes your application again.

Why was the exception thrown?

The exception was thrown because you attempted to commit a FragmentTransaction after the activity's state had been saved, resulting in a phenomenon known as Activity state loss. Before we get into the details of what this actually means, however, let's first take a look at what happens under-the-hood when onSaveInstanceState() is called. As I discussed in my last post about Binders & Death Recipients, Android applications have very little control over their destiny within the Android runtime environment. The Android system has the power to terminate processes at any time to free up memory, and background activities may be killed with little to no warning as a result. To ensure that this sometimes erratic behavior remains hidden from the user, the framework gives each Activity a chance to save its state by calling its onSaveInstanceState() method before making the Activity vulnerable to destruction. When the saved state is later restored, the user will be given the perception that they are seamlessly switching between foreground and background activities, regardless of whether or not the Activity had been killed by the system.

When the framework calls onSaveInstanceState(), it passes the method a Bundle object for the Activity to use to save its state, and the Activity records in it the state of its dialogs, fragments, and views. When the method returns, the system parcels the Bundle object across a Binder interface to the System Server process, where it is safely stored away. When the system later decides to recreate the Activity, it sends this same Bundle object back to the application, for it to use to restore the Activity's old state.

So why then is the exception thrown? Well, the problem stems from the fact that these Bundle objects represent a snapshot of an Activity at the moment onSaveInstanceState() was called, and nothing more. That means when you call FragmentTransaction#commit() after onSaveInstanceState() is called, the transaction won't be remembered because it was never recorded as part of the Activity's state in the first place. From the user's point of view, the transaction will appear to be lost, resulting in accidental UI state loss. In order to protect the user experience, Android avoids state loss at all costs, and simply throws an IllegalStateException whenever it occurs.

When is the exception thrown?

If you've encountered this exception before, you've probably noticed that the moment when it is thrown is slightly inconsistent across different platform versions. For example, you probably found that older devices tended to throw the exception less frequently, or that your application was more likely to crash when using the support library than when using the official framework classes. These slight inconsistencies have led many to assume that the support library is buggy and can't be trusted. These assumptions, however, are generally not true.

The reason why these slight inconsistencies exist stems from a significant change to the Activity lifecycle that was made in Honeycomb. Prior to Honeycomb, activities were not considered killable until after they had been paused, meaning that onSaveInstanceState() was called immediately beforeonPause(). Beginning with Honeycomb, however, Activities are considered to be killable only after they have been stopped, meaning that onSaveInstanceState() will now be called before onStop() instead of immediately before onPause(). These differences are summarized in the table below:

  pre-Honeycomb post-Honeycomb
Activities can be killed before onPause()? NO NO
Activities can be killed before onStop()? YES NO
onSaveInstanceState(Bundle) is guaranteed to be called before... onPause() onStop()

As a result of the slight changes that were made to the Activity lifecycle, the support library sometimes needs to alter its behavior depending on the platform version. For example, on Honeycomb devices and above, an exception will be thrown each and every time a commit() is called afteronSaveInstanceState() to warn the developer that state loss has occurred. However, throwing an exception every time this happened would be too restrictive on pre-Honeycomb devices, which have their onSaveInstanceState() method called much earlier in the Activity lifecycle and are more vulnerable to accidental state loss as a result. The Android team was forced to make a compromise: for better inter-operation with older versions of the platform, older devices would have to live with the accidental state loss that might result between onPause() and onStop(). The support library's behavior across the two platforms is summarized in the table below:

  pre-Honeycomb post-Honeycomb
commit() before onPause() OK OK
commit() between onPause() and onStop() STATE LOSS OK
commit() after onStop() EXCEPTION EXCEPTION

How to avoid the exception?

Avoiding Activity state loss becomes a whole lot easier once you understand what is actually going on. If you've made it this far in the post, hopefully you understand a little better how the support library works and why it is so important to avoid state loss in your applications. In case you've referred to this post in search of a quick fix, however, here are some suggestions to keep in the back of your mind as you work with FragmentTransactions in your applications:

  • Be careful when committing transactions inside Activity lifecycle methods. A large majority of applications will only ever commit transactions the very first time onCreate() is called and/or in response to user input, and will never face any problems as a result. However, as your transactions begin to venture out into the other Activity lifecycle methods, such as onActivityResult(),onStart(), and onResume(), things can get a little tricky. For example, you should not commit transactions inside the FragmentActivity#onResume() method, as there are some cases in which the method can be called before the activity's state has been restored (see the documentation for more information). If your application requires committing a transaction in an Activity lifecycle method other than onCreate(), do it in either FragmentActivity#onResumeFragments() orActivity#onPostResume(). These two methods are guaranteed to be called after the Activity has been restored to its original state, and therefore avoid the possibility of state loss all together. (As an example of how this can be done, check out my answer to this StackOverflow question for some ideas on how to commit FragmentTransactions in response to calls made to theActivity#onActivityResult() method).

  • Avoid performing transactions inside asynchronous callback methods. This includes commonly used methods such as AsyncTask#onPostExecute() andLoaderManager.LoaderCallbacks#onLoadFinished(). The problem with performing transactions in these methods is that they have no knowledge of the current state of the Activity lifecycle when they are called. For example, consider the following sequence of events:

    1. An activity executes an AsyncTask.
    2. The user presses the "Home" key, causing the activity's onSaveInstanceState() and onStop()methods to be called.
    3. The AsyncTask completes and onPostExecute() is called, unaware that the Activity has since been stopped.
    4. FragmentTransaction is committed inside the onPostExecute() method, causing an exception to be thrown.

    In general, the best way to avoid the exception in these cases is to simply avoid committing transactions in asynchronous callback methods all together. Google engineers seem to agree with this belief as well. According to this post on the Android Developers group, the Android team considers the major shifts in UI that can result from committing FragmentTransactions from within asynchronous callback methods to be bad for the user experience. If your application requires performing the transaction inside these callback methods and there is no easy way to guarantee that the callback won't be invoked after onSaveInstanceState(), you may have to resort to usingcommitAllowingStateLoss() and dealing with the state loss that might occur. (See also these two StackOverflow posts for additional hints, here and here).

  • Use commitAllowingStateLoss() only as a last resort. The only difference between callingcommit() and commitAllowingStateLoss() is that the latter will not throw an exception if state loss occurs. Usually you don't want to use this method because it implies that there is a possibility that state loss could happen. The better solution, of course, is to write your application so thatcommit() is guaranteed to be called before the activity's state has been saved, as this will result in a better user experience. Unless the possibility of state loss can't be avoided,commitAllowingStateLoss() should not be used.

Hopefully these tips will help you resolve any issues you have had with this exception in the past. If you are still having trouble, post a question on StackOverflow and post a link in a comment below and I can take a look. :)

As always, thanks for reading, and leave a comment if you have any questions. Don't forget to +1 this blog and share this post on Google+ if you found it interesting!

Fragment Transactions & Activity State Loss的更多相关文章

  1. Fragment提交transaction导致state loss异常

    下面自从Honeycomb发布后,下面栈跟踪信息和异常信息已经困扰了StackOverFlow很久了. java.lang.IllegalStateException: Can not perform ...

  2. Fragment Transactions和Activity状态丢失

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

  3. Fragment、Activity 保存状态

    Activity 保存状态1. void onCreate(Bundle savedInstanceState) 当Activity被第首次加载时执行.我们新启动一个程序的时候其主窗体的onCreat ...

  4. Fragment 与Activity

    一个Activity 对应 多个Fragment; 每一个类 extends Fragment , 一个Activity 可以同时显示多个 Fragment; Fragment是依赖于Activity ...

  5. Fragment与Activity之间的通信

      我个人将Fragment与Activity间的通信比喻为JSP与Servlet间的通信,fragment中用接口的方式来进行与Activity的通信.通信的结果可以作为数据传入另一个Fragmen ...

  6. Android系列之Fragment(三)----Fragment和Activity之间的通信(含接口回调)

    ​[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...

  7. 深入分析:Fragment与Activity交互的几种方式(一,使用Handler)

    这里我不再详细介绍那写比较常规的方式,例如静态变量,静态方法,持久化,application全局变量,收发广播等等. 首先我们来介绍使用Handler来实现Fragment与Activity 的交互. ...

  8. Fragment和Activity的区别

    Fragment用来描述一些行为或一部分用户界面在一个Activity中,可以合并多个Fragment在一个单独的Activity中建立多个UI面板,同时重用Fragment在多个activity中. ...

  9. Fragment 与 Activity 通信

    先说说背景知识: (From:http://blog.csdn.net/t12x3456/article/details/8119607) 尽管fragment的实现是独立于activity的,可以被 ...

随机推荐

  1. .NetCore下使用Prometheus实现系统监控和警报 (四)客户端代码处理

    在代码中使用就比较简单了 Nuget包获取下 prometheus-net prometheus-net.AspNetCore 然后添加中间件就行了 app.UseMetricServer(); 默认 ...

  2. appium入门级教程(3)—— 安装 Android SDK

    前言 搭建Android平台不是必须的,如果你不想使用 Android 模拟器运行测试的话可以跳过,不过,建议安装:原生 Android 好折腾!关键是它自带的一些工具是做 appium 测试必须要用 ...

  3. Visual Studio 2013 发布正式版及使用感受

    (注意:文末我添加了一个小节,用来更新最新的一些使用感受.) 昨天晚上,微软在正式发布Windows 8.1的同时,也同时发布了Visual Studio 2013产品系列的正式版. 版本情况 Vis ...

  4. 工作流调度器azkaban2.5.0的安装和使用

    为什么需要工作流调度系统 一个完整的数据分析系统通常都是由大量任务单元组成: shell脚本程序,java程序,mapreduce程序.hive脚本等 各任务单元之间存在时间先后及前后依赖关系 为了很 ...

  5. 【noip模拟赛1】古韵之同心锁

    据说在一座OI桥上,同心锁上显示的文字有着它奇异的呈现方式,需要你把它稍做改变才可解密.每个同心锁上都有3个数据.第一个数据是一个字符串s.第二个数据m表示把s串从m处分为两段,s[1]至s[m-1] ...

  6. 【Java】 大话数据结构(9) 树(二叉树、线索二叉树)

    本文根据<大话数据结构>一书,对Java版的二叉树.线索二叉树进行了一定程度的实现. 另: 二叉排序树(二叉搜索树) 平衡二叉树(AVL树) 二叉树的性质 性质1:二叉树第i层上的结点数目 ...

  7. CMU-15445 LAB1:Extendible Hash Table, LRU, BUFFER POOL MANAGER

    概述 最近又开了一个新坑,CMU的15445,这是一门介绍数据库的课程.我follow的是2018年的课程,因为2018年官方停止了对外开放实验源码,所以我用的2017年的实验,但是问题不大,内容基本 ...

  8. 深入分析Spring Boot2,解决 java.lang.ArrayStoreException异常

    将某个项目从Spring Boot1升级Spring Boot2之后出现如下报错,查了很多不同的解决方法都没有解决: Spring boot2项目启动时遇到了异常: java.lang.ArraySt ...

  9. JMS Java消息服务(Java Message Service)

    JMS 在一些场景下RPC的同步方式可能不太适合业务逻辑的处理,并且这种方式在某些场景下会导致业务的紧耦合. 基于异步交互模型的JMS解决了RPC产生的紧耦合问题,它提供了一个可以通过网络访问的抽象消 ...

  10. DML和DQL 总结

    一:MySql的存储引擎 问题的引入: 由于不同用户对数据的容量,访问速度,数据安全性有着不同的要求. 为了满足不同用户的需求,mysql数据库采用多种存储引擎来进行数据的存储! 1.1:查询mysq ...