Fragment Transactions Activity State Loss
自己以前在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相关推荐
- Fragment提交transaction导致state loss异常
下面自从Honeycomb发布后,下面栈跟踪信息和异常信息已经困扰了StackOverFlow很久了. java.lang.IllegalStateException: Can not perform ...
- Fragment Transactions和Activity状态丢失
本文由 伯乐在线 - 独孤昊天 翻译.未经许可,禁止转载! 英文出处:androiddesignpatterns.欢迎加入翻译组. 下面的堆栈跟踪和异常代码,自从Honeycomb的初始发行版本就一直 ...
- android fragment activity 交互,Android基础之Fragment与Activity交互详解
今天继续讲解Fragment组件的特性,主要是跟Activity的交互和生命周期的关系,我们前面已经说过Fragment是依赖于Activity的,而且生命周期也跟Activity绑定一起.下面我们看 ...
- android t跳转到fragment,Android 使用EventBus进行Fragment和Activity通信
本文介绍EventBus的基本使用,以及用于Fragment和Activity之间通信. github地址: https://github.com/greenrobot/EventBus 版本是 Ev ...
- Fragment与Activity交互(使用接口)
在Fragment中: 1. // 定义一个回调接口,该Fragment所在Activity需要实现该接口// 该Fragment将通过该接口与它所在的Activity交互 { public void ...
- Fragment与Activity传递数据
MainActivity如下: package cc.testsimplefragment0;import android.os.Bundle; import android.app.Activity ...
- Android(Fragment和Activity之间通信)
Fragment的使用可以让我们的应用更灵活的适配各种型号的安卓设备,但是对于Fragment和Activity之间的通信,很多朋友应该比较陌生,下面我们就通过一个实例来看一看如何实现. 一.Acti ...
- 静态注册fragment_Fragment的静态和动态添加方式以及Fragment和Activity之间的通信方式...
一.静态添加方式:创建好Fragment之后,在需要使用碎片的Activity的布局文件中添加 标签. 二.动态添加方式:先创建好fragment的布局文件,然后创建一个继承自Fragment的 类( ...
- Fragment与Activity之间的相互通信
https://blog.csdn.net/u012702547/article/details/49786417 https://blog.csdn.net/carson_ho/article/de ...
最新文章
- HikariPool使用MySQL/MariaDB数据库报错解决:java.sql.SQLException: Access denied for user 'root'@'localhost' (u
- java param request_使用@RequestParam将请求参数绑定至方法参数
- linux如何设置账号全民,linux基本练习:用户和组管理的相关练习
- android下raw目录的作用,Android 之 assets目录和raw目录
- 455. 分发饼干 golang
- 邮箱如何秘密发送多个人邮件_如何发送秘密消息
- 带参数的URLconf
- Redis五种数据类型及应用场景
- 【苹果cms10 Maccmsv10 站群深度定制版 开发日志】 新增日志模块
- 20201023:力扣第37场双周赛(上)
- 广搜,智能拼图(ZOJ1079)
- ArcGIS——数据库与服务备份(一、oracle中的geodatabase备份使用impdp与expdp)
- 常见经典排序算法学习总结(插入、shell、冒泡、选择、归并、快排等)
- int main ( int argc, char** argv )的说明
- wso2 mysql_windows下 WSO2 Application Server配置 及 MySQL数据服务部署
- 【原创】2021-2000上市公司重污染企业数据、上市公司重污染行业数据(常用变量均包括,可直接用)
- 云知声发布多模态AI芯片战略,同步曝光三款在研芯片...
- mach_absolute_time 高效计算时间差
- 计算机黑屏修改设置,电脑黑屏密码怎么设置
- Win10微软输入法取消繁体简体切换快捷键的方法