自己以前在Fragment中直接增加和移除DialogFragment时,遇到了一个比较奇怪的异常。
当时找了一下相关的资料,觉得一篇英文文章分析的不错。
于是,一直想抽时间翻译并做记录。

后来自己各种拖延,加上不得已穿插做了一些其它的事,
直到今天才完成了翻译,有些汗颜。

原文链接:http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html


自从Android 3.0版本发布以来,StackOverflow上就出现了大量类似下面异常栈信息的问题:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceStateat 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的状态被系统保存后,开发人员仍试图提交一个FragmentTransaction,
于是就会导致被称为Activity State loss的现象。


在进一步了解上述现象的细节前,我们不妨先看看系统调用onSaveInstanceState时,
在底层“暗中”进行的工作。

在Android运行环境下,android应用实际上难以掌控自己的命运。
为了释放内存,Android系统有权力在任何时间杀死应用所在进程,
因此后台Activity可能在没有任何提示或警告下就被杀掉了。

为了减少系统的这种行为对用户造成的影响,Android框架在销毁Activity前,
给了Activity一个机会去调用onSaveInstanceState,以保存自己的状态。

当用户重新启动被杀死的Activity时,
之前被保存的状态将被恢复。

于是,无论Activity之前是否被系统杀死,
用户都只会有一种在前台和后台无缝切换Activity的感觉。


当Framework调用onSaveInstanceState方法时,传入了一个Bundle对象供Activity使用,
Activity会在Bundle中记录自身dialog,fragment及view的状态。

当onSaveInstanceState返回时,系统会打包Bundle对象,
并通过Binder接口将其发送给System Server进程。
由于SystemServer进程可以常驻,因此可以安全地保留Activity的状态信息。

当系统决定重新创建Activity时,
会将之前保存的包含Activity状态信息的Bundle发送给对应应用,
应用将根据Bundle中的信息恢复Activity的状态。


在了解上述原理后,我们就可以理解上述异常抛出的原因了。

当Activity调用onSaveInstanceState时,
记录状态信息的Bundle对象将作为Activity当前状态的快照。

因此,如果开发人员在onSaveInstanceState之后,
才调用FragmentTransaction的commit接口,
那么这次transaction不会被Bundle对象记录。

于是这次transaction就可能由于Activity异常终止而丢失,
即出现state loss。

为了保证用户体验,Android尽可能地避免state loss,
当出现这种情况时,就直接抛出IllegalStateException。


二、何时抛出异常

如果你也遇到过这种异常,
就可能也发现这种异常抛出的时机,
在不同的平台版本中,具有轻微的差异。

例如,你可能发现应用在旧版本的设备上抛出异常的频率较低;
或者使用支持库crash的概率,明显大于使用官方库的概率。
这些差异使得大多数用户怀疑支持库存在问题,
然而这种猜测是错误的。

造成这种差异性的真正原因是:
Android在Honeycomb版本,对Activity的生命周期进行了调整。
在Honeycomb版本之前,Activity在paused之前是无法被杀死的,
这意味者onSaveInstanceState将在onPause之前被调用。

从Honeycomb开始,Activity只有在onStop之后才能被杀死,
于是onSaveInstanceState将在onStop之前被调用,而不是onPause。

这种差异性如下表所示:

考虑到Activity生命周期在不同版本之间的差异,
支持库有时候需要根据版本调整它们的行为。

例如:在Honeycomb及之后版本的设备上,一旦Activity在onSaveInstanceState后有commit提交,
那么系统一定会抛出异常来提醒开发人员出现state loss。

然而,在Honeycomb之前的版本中,却不一定会抛出异常。

支持库在Honeycomb之前和之后版本上的表现类似于下表:

可见旧版本中,onSaveInstanceState在Activity生命周期中被调用的位置更加靠前,
更有可能受到state loss问题的影响。


三、如何避免这种异常

了解Activity state loss的原因后,避免其发生就比较简单了。
这里给出几条使用FragmentTransactions时,避免state loss的建议。

1、在Activity的生命周期函数中提交transactions时需要注意。

大多数应用,仅会在Activity的onCreate第一次被调用或响应用户输入时,
才进行提交transactions的操作。
在这种情况下,应用是不会遇到state loss这种问题。

然而,如果应用在Activity的其它生命周期函数,
例如onActivityResult、onStart和onResume中提交transaction时,
情况就变得比较微妙了。

例如,开发人员不应该在FragmentActivity的onResume函数中提交transaction。
因为在有些场景下,FragmentActivity的onResume函数将在它的状态被完全恢复前调用。

为了尽可能地避免state loss,
当应用需要在Activity的onCreate之外的生命周期函数中提交transaction时,
最好保证是在FragmentActivity的onResumeFragments函数,
或Activity的onPostResume函数中进行。

这两个方法都保证当Activity恢复了之前的状态时才被调用,
因此都能避免state loss发生。

2、避免在异步回调方法中提交transaction。

这里的异步回调方法通常指的是类似AsyncTask的onPostExecute方法、
LoaderManager.LoaderCallbacks的onLoadFinished方法等。

在这些回调函数中处理transaction的问题在于:
当函数被回调时,并不清楚Activity当前的状态。

例如,如果事件按照下列时序发生:
一个Activity启动了一个AsyncTask。

然后,在AsyncTask执行的过程中,用户点击了Home键,
导致Activity的onSaveInstanceState和onStop函数被调用。

当AsyncTask完成,并回调onPostExecute时,
并不清楚Activity已经stop了。

如果此时在onPostExecute中哦给你进行FragmentTransaction的commit操作,
就会导致异常。

因此,一般情况下为了避免这种情况发生,
应该尽量避免在异步回调函数中提交transaction。

Google官方文档似乎也认可这个观点。
在文档中提到了:在异步回调函数中,
进行FragmentTransactions的commit操作,
以带来UI的变化,将导致不好的用户体验。

如果应用一定要在异步回调函数中提交transaction,
那么没有办法保证回调函数在onSaveInstanceState之前执行。

此时,需要借助于FragmentTransaction的commitAllowingStateLoss函数,
并自己处理可能的state loss。

3、commitAllowingStateLoss仅作为最后的解决手段。

FragmentTransaction的commit和commitAllowingStateLoss函数唯一的区别是,
后者在发生state loss时不会抛出异常。

通常,开发人员不应该使用commitAllowingStateLoss,
因为这意味开发人员知道state loss有可能发生,
但没有采取积极的策略去解决。

此时,最好的解决方案应该是改写代码,
保证在Activity的state被保存前,
调用FragmentTransaction的commit方法,
这样才能带来更好的用户体验。

总之,除非state loss实在无法避免,
否则尽量不要使用commitAllowingStateLoss。

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. android fragment activity 交互,Android基础之Fragment与Activity交互详解

    今天继续讲解Fragment组件的特性,主要是跟Activity的交互和生命周期的关系,我们前面已经说过Fragment是依赖于Activity的,而且生命周期也跟Activity绑定一起.下面我们看 ...

  4. android t跳转到fragment,Android 使用EventBus进行Fragment和Activity通信

    本文介绍EventBus的基本使用,以及用于Fragment和Activity之间通信. github地址: https://github.com/greenrobot/EventBus 版本是 Ev ...

  5. Fragment与Activity交互(使用接口)

    在Fragment中: 1. // 定义一个回调接口,该Fragment所在Activity需要实现该接口// 该Fragment将通过该接口与它所在的Activity交互 { public void ...

  6. Fragment与Activity传递数据

    MainActivity如下: package cc.testsimplefragment0;import android.os.Bundle; import android.app.Activity ...

  7. Android(Fragment和Activity之间通信)

    Fragment的使用可以让我们的应用更灵活的适配各种型号的安卓设备,但是对于Fragment和Activity之间的通信,很多朋友应该比较陌生,下面我们就通过一个实例来看一看如何实现. 一.Acti ...

  8. 静态注册fragment_Fragment的静态和动态添加方式以及Fragment和Activity之间的通信方式...

    一.静态添加方式:创建好Fragment之后,在需要使用碎片的Activity的布局文件中添加 标签. 二.动态添加方式:先创建好fragment的布局文件,然后创建一个继承自Fragment的 类( ...

  9. Fragment与Activity之间的相互通信

    https://blog.csdn.net/u012702547/article/details/49786417 https://blog.csdn.net/carson_ho/article/de ...

最新文章

  1. HikariPool使用MySQL/MariaDB数据库报错解决:java.sql.SQLException: Access denied for user 'root'@'localhost' (u
  2. java param request_使用@RequestParam将请求参数绑定至方法参数
  3. linux如何设置账号全民,linux基本练习:用户和组管理的相关练习
  4. android下raw目录的作用,Android 之 assets目录和raw目录
  5. 455. 分发饼干 golang
  6. 邮箱如何秘密发送多个人邮件_如何发送秘密消息
  7. 带参数的URLconf
  8. Redis五种数据类型及应用场景
  9. 【苹果cms10 Maccmsv10 站群深度定制版 开发日志】 新增日志模块
  10. 20201023:力扣第37场双周赛(上)
  11. 广搜,智能拼图(ZOJ1079)
  12. ArcGIS——数据库与服务备份(一、oracle中的geodatabase备份使用impdp与expdp)
  13. 常见经典排序算法学习总结(插入、shell、冒泡、选择、归并、快排等)
  14. int main ( int argc, char** argv )的说明
  15. wso2 mysql_windows下 WSO2 Application Server配置 及 MySQL数据服务部署
  16. 【原创】2021-2000上市公司重污染企业数据、上市公司重污染行业数据(常用变量均包括,可直接用)
  17. 云知声发布多模态AI芯片战略,同步曝光三款在研芯片...
  18. mach_absolute_time 高效计算时间差
  19. 计算机黑屏修改设置,电脑黑屏密码怎么设置
  20. Win10微软输入法取消繁体简体切换快捷键的方法

热门文章

  1. deny后加to do还是doing_为什么英语中有些动词后只能接 doing,而不能接 to do?
  2. UI靠近边框解决办法
  3. 如何使用ping命令检查网络情况
  4. 被 KPI 绑架的百度贴吧
  5. unity AudioToolkit 音频工具包的简介+使用方法
  6. FPGA实现CortexM3内核
  7. Android、Java要收费了!学霸程序员怒捅马蜂窝,沙特记者命运?GitHub挂了!
  8. 高新技术企业申请后多久会出结果呢?
  9. java viewer 控件_插件制作过程记录(使用TreeViewer贡献视图)
  10. VlC转推播放的视频到虚拟摄像头