Fragment处理与Activity状态丢失(State Loss)

自从Android 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)

这篇文章将会解释这个异常为何以及何时会出现,并且总结几条建议保证能够帮助你的程序再也不会因此而崩溃了.

为何这个异常会产生?

这个异常之所以会产生,源于在activity状态被保存之后又尝试提交一个 FragmentTransaction,结果导致一种被称为Activity状态丢失的现象(Activity state loss).在我们深入了解之前,我们先看看在 onSaveInstanceState()被调用之后发生了一些什么.在我上篇名叫 Binders & Death Recipients的博客中讲到过,Android程序几乎不能自己在Android运行环境中的生命周期.Android系统有权利在任意时刻终止进程以释放内存,导致后台的activities可能在毫无提示的情况下被终止.为了保证这种不稳定的情况对用户透明,底层framework给每个Activity一个机会在被系统倾向于回收之前通过调用 onSaveInstance()来保存自己的状态.当这种状态在后来被恢复时,用户再切换回后台Activity时,是不会感觉到这个Activity之前被系统回收过.

当底层framework调用 onSaveInstance(),它会传入一个Bundle对象作为方法的参数让Activity将自己的状态保存在里面,然后Activity记录下自己中dialog,fragments,和views.当方法返回时,系统通过一个系统服务进程的Binder接口将这个Bundle对象序列化保存到一个安全的地方.当后面系统决定要重新创建Activity的时候,它将会传入这个Bundle对象回程序中,利用它来恢复Activity之前的状态.

所以为什么这个异常会产生呢?这个问题源于这些Bundle对象在 onSaveInstance()被调用时仅代表着Activity的快照.这也就意味着当你在 onSaveInstance()之后调用 FragmentTransaction#commit(),那么transaction是不会被保存的因为它从来没有被当做Activity状态的一部分被保存起来.从用户的角度来看,transaction有可能就丢失了,导致UI意外的状态丢失.为了保证用户的体验,Android在任何情况下都会避免状态丢失,并且在这种情况发生时就简单的抛出一个 IllegalStateException异常.

这个异常何时抛出?

如果你之前遇到过这个异常,可能你已经发现了在不同的系统上它有些许的不同.例如,你可能发现老的设备并没有那么频繁的出现这个异常,或者你的程序在你使用了support包而不是官方framework的类后会更多崩溃.这些轻微的不同导致了许多人认为support包有bug并且是不可靠的,这些假设其实都是不正确的.

这些不同产生的原因在于Honeycomb之后Activity的生命周期有了一些变化,在Honeycomb之前,Activities在pause状态之前是不会被系统回收的,意味着 onSaveInstance()一被调用onPause就接着被调用了.然而在Honeycomb之后,Activities只能在stop状态之后才能被回收,意味着 onSaveInstance()现在会在 onStop()之前执行,而不是之前的 onPause().这些区别如下:

  Honeycomb之前 Honeycomb之后
Activities 能在 onPause()之前被回收? NO NO
Activities 能在 onStop()之前被回收? YES NO
onSaveInstanceState(Bundle) 保证在在____之前被调用 onPause() onStop()

 

结果导致了Activity生命周期的一些细微变化,support包有时需要声明它执行时所依赖的系统版本,例如,在Honeycomb之后,每次在onSaveInstance()之后调用commit()都会抛出一个异常警告开发者状态丢失的发生,然而,在Honeycomb之前,这种限制将更加严格,因为它们的onSaveInstance()方法在Activity生命周期中更早的被调用了,导致他们容易因为状态丢失发生错误.Android开发者被迫让步:为了在旧版本上体验更好的交互操作,老设备必须接受在onPause()onStop()之间引起的偶然的状态丢失.

support包在两种系统上的总结如下:

  Honeycomb之前 Honeycomb之后
commit() 在 onPause()前 OK OK
commit() 在onPause() 和onStop()之间 STATE LOSS OK
commit() after onStop() EXCEPTION EXCEPTION


如何避免异常?

当你明白原理了以后,避免Activity状态丢失就显得十分轻松.如果你做到这一步,希望你能更进一步了解support包的工作原理和为何在你的程序中避免状态丢失是如此的重要.假如你阅读这篇文章是为了找到一个快速解决的方法,这里有一些建议请你在使用FragmentTransactions开发自己的程序时牢记脑海中:
  • 在Activity生命周期方法中commit transaction时要十分注意.大多数的程序调用commit transactions仅仅在初次创建时的onCreate()方法中和(或)当用户有输入动作时的UI反馈,因此不会遇到问题.然而当你的程序开始在别的生命周期方法,例如onActivityResult(),onStart()中尝试使用时,可以使用一些小技巧.例如,你绝不应该在FragmentActivity#onResume()中使用,因为在某些时刻,这个方法会在Activity的状态被恢复之前调用(参考).如果你的程序需要在onCreate()之外的生命周期中commit transaction,可以在FragmentActivity#onResumeFragments()或者Activity#onPostResume()中.这两个方法肯定是在Activity状态恢复之后调用的,因此可以保证避免状态丢失.(例子可以参考原作者在StackOverflow上的回答,如何在Activity#onActivityResult()中commit FragmentTransactions).
  • 避免在异步回调函数中进行transaction.这包括常用的方法像AsyncTask#onPostExecute()LoaderManager.LoaderCallback#onLoadFinished().在这些方法中进行transaction的问题在于它们被调用时并不知道当前Activity生命周期的状态如何.例如,假设有以下事件:
  1. 一个Activity执行了一个AsyncTask.
  2. 用户按下"Home"键,导致activity的onSaveInstanceState()onStop()方法被调用.
  3. AsyncTask完成,并执行onPostExecute()被调用,并不知道Activity已经stop了.
  4. 一个FragmentTransaction在onPostExecute()方法中被执行,导致抛出异常.
总之,对于以上来说最好的办法就是不在异步回调函数中commit transactions.Google工程师看来很坚信这一信念.通过这篇在Android Developers group上的文章,Android团队认为通过在异步回调中commit FragmentTransactions 来进行主要的UI变化对用户体验来说是不好的.如果你的程序需要在在这些回调方法中执行transaction,那么没有一种简单的方法能保证回调不在onSaveInstanceState()之后执行,你可能需要使用commitAllowingStateLoss()并且做好状态丢失可能发生的准备了.(参考1,参考2).
  • 使用commitAllowingStateLoss()作为最后的手段.commit()commitAllowingStateLoss()之间唯一的区别在于后者在状态丢失时不会抛出异常.通常你不会希望使用这个方法的,因为这意味着程序有可能出现状态丢失.最好的解决办法,当然是保证你的程序中的commit()在Activity状态保存前被调用了,同时这也能给用户最好的体验.除非状态丢失无法避免,否则commitAllowingStateLoss()绝不该被使用.


  • 原文地址: http://www.aiuxian.com/article/p-1874919.html
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值