Fragment的展现与Activity状态丢失 IllegalStateException:Can not perform this action after onSaveInstanceState

下面的异常信息自从Android Honeycomb问世后,在stackoverflow.com就不但出现,显然,它困扰了很多的开发者

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)

这篇文章将介绍为什么这个异常会被抛出,并且给出几个建议来保证这个异常再也不会在你的应用程序里出现。

这个异常怎么被抛出的

这个异常被抛出是因为开发者在Activity保存了状态尝试去做FragmentTransaction的commit操作,导致了这种成为Activity状态丢失的现象。在我们深入了解这个异常的原因前,我们先看下载Activity的onSaveInstanceState()的调用后发生了什么。正如我在上篇博客Binders & Death Recipients 写的那样,Android应用程序对于它的生命周期的控制是非常弱的。Android系统拥有权力来结束进程以达到释放内存的作用,而且那些被结束进程的Activity甚至可能只能收到一点乃至一点都没有的警告信息。为了保证这样的行为让用户感知不到,android 系统层会调用即将销毁Activity的onSaveInstanceState()来给Activity机会保存它的信息。当下次这些保存的信息被恢复的时候,用户可以轻松地看到Activity从后台切换到前台,而不需要知道Activity之前是否被销毁了。
当系统层调动onSaveInstanceState(),它传递给这个方法一个Bundle给Activity来保存它的状态,包括Activity里dialog,fragment,view的状态。当这个方法返回,系统通过跨进程的Binder接口把这个bundle传递给系统服务进程,这个bundle将安全地存在系统服务进程里。当系统层下次决定要重新创建这个Activity的时候,系统服务进程会把这个Bundle传给对应的应用程序,让它来恢复Activity的原有状态。

这个异常什么时候被抛出

如果你之前有遇到这个问题,你可能注意到它被抛出的时机在不同版本的Android系统不大一样。比如,你可能发现老一点版本的Android系统比较少抛出这个异常或者你的应用程序在使用suppor包的时候比使用原生的系统层类更容易发生抛出这个异常。这个细微的差别让很多人认为support包邮bug因此不能信任。这些假设,其实都是不对的。
为什么会有这些不同的表现的真正原因是因为Activity的生命周期在Honeycomb出现后有了些许的差别。在Honeycomb之前,Activity是不可被杀死了直到它们被暂停了,也就是说onSaveInstanceState()在要执行onPause()前就被调用了。而从Honeycomb开始,Activity在它们被停止了就才可以销毁,即只有Activity的onSaveInstanceState()会在onStop()之前调用。这个不同可以用如下表格总结:

Honeycomb之前Honeycomb之后 (high)
Activity在onPause()前会被销毁?不会不会
Activity在onStop()前会被销毁?不会
onSaveInstanceState(Bundle)确保在哪个方法前调用onPauseonStop

正是因为Activity在生命周期上的细微变化导致support库需要根据不同版本系统有不同的表现。比如,在Honeycomb版本之上的设备,这样一个异常信息会在onSaveInstanceState()被调用后进行commit()操作后出现,以此来警告开发者Activity的状态丢失了。但是,如果设备的系统版本Honeycomb之前的,每次onSaveInstanceState()被调用后进行commit()就会抛出这个异常的话就会显得这个Activity非常容易丢失状态,因为它们的onSaveInstanceState()的调用要更早。因此,Android团队只好做出一个妥协,为了更好兼容旧版本,系统比较旧的设备就必须克服发生在onPause和onStop之间的可能的状态丢失,support库在不同版本系统上的表现如下:

Honeycomb之前Honeycomb之后 (high)
在onPause()之前commit()正常正常
在onPause()和onStop()之间commit()状态丢失正常
在onStop()之后commit()异常异常

怎么防止异常发生

当你知道为什么发生Activity状态丢失时,如何阻止它变得容易多了。如果你在看到这篇博客之前就知道如何防止这种状况,希望这篇博客能让你知道一点support库如何工作的并且为什么防止状态丢失是如此重要。你可能查到这篇文章是为了找到一个快速修复这个问题的办法,但,这里还是有些建议让你在使用FragmentTransaction需要注意的地方:

  • 在Activity的生命周期函数里谨慎使用commit,大部分的应用只会在Activity的第一次调用onCreate或者相应用户输入的时候使用commit,这两者情况下都不可能遇到这个问题。但,一旦在其他Activity的生命周期函数,比如onActivityResult(),onStart(),onResume(),就变得有点麻烦了。比如,你不应该在FragmentActivity的onResume()里commit,因为存在一些情况onResume()会在Activity恢复信息前调用(具体查看文档)。如果你的应用程序需要在Activity非onCreate()的生命周期函数里commit,那么就在FragmentActivity的onResumeFragments()或者Activity#onPostResume()。这两个方法都会在Activity从它的销毁状态恢复过来后才会调用,因此可以避免状态丢失。(这里有个例子,是我对StackOverflow问题这个问题的回答,可能可以给你一点关于如何在Activity#onActivityResult()做commit的思路)。
  • 防止在异步操作的回调方法里执行transaction操作。这里包括在AsyncTask#onPostExecute() 和LoaderManager.LoaderCallbacks#onLoadFinished()等方法里的操作。在这些方法里操作commit等操作的问题在于这些方法没法活儿当前Activity的生命周期状态。比如考虑如下顺序的事件:
    1、一个Activity执行了AsyncTask
    2、用户按了Home键,触发了onSaveInstanceState()和onStop()方法。
    3、AsyncTask异步操作完成回调了onPostExecute(),并且不知道Activity已经onStop,导致抛出异常。
    一般来说,去好的防止在这些异步回调方法里发生异常的方法是不要触发这些方法。Google似乎也很赞同这个观点。根据这篇文章,Android团队也认为在异步回调里做commit操作对于用户体验并不是一种好的体验。如果你的应用程序要求在这些方法里进行commit等操作,并没有合适的方式保证回调在onSaveInstanceState()后触发,你可能只能使用commitAllowingStateLoss()并处理可能随之发生的状态丢失。(可以看这两个问题来理解这个问题链接1链接2)

  • 把commitAllowingStateLoss()作为没有其他办法才使用的方法,commit和commitAllowingStateLoss方法的差别就在于后者不会抛出在状态丢失的时候抛出异常。通常你最好不要使用这个方法,因为这种情况下可能存在状态丢失。更好的方法,是保证你的应用程序在状态丢失前使用commit,因为这会是更好的用户体验。除非状态丢失的事情无法避免,否则不要使用commitAllowingStateLoss().

    希望这些方法能帮你解决这个异常。如果你还有问题,可以在StackOverflow 提出问题并在下面的评论里把链接给我,我会去瞧瞧。
    和以往一样,感谢阅读,如果有任何问题可以在评论里留言。别忘了赞这边博客,如果你觉得这篇文章有趣,可以把它分享到你的社交网络里。

本文翻译自Fragment Transactions & Activity State Loss 翻译后的标题做一定修改来让这边翻译更能被人搜索到。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值