Can not perform this action after onSaveInstanceState

问题

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的状态被保存后,即调用了onSaveInstanceState后,又尝试提交FragmentTransaction。

而为啥会有这个onSaveInstanceState?

1) 横竖屏的切换,会导致界面重绘,从而触发到这个方法 2)【根本问题】安卓系统在内存紧张的时候,会中止掉某些进程,并且后台的activitys会被隐式的被杀掉;为了将这一行为对用户不可知,系统会给每个activity保存状态的回调,使得被销毁前执行操作。这样,当数据被恢复时,用户只是感觉Activity在前后台的切换,而不是activity被系统杀死过。

具体的过程

当框架调用onSaveInstanceState()的时候,它会向这个方法传递一个Bundle参数,Activity可以用这个参数来保存页面、对话框、Fragments和视图的状态。当onSaveInstanceState返回时,会将一个Bundle对象序列化之后通过Binder接口传递给系统服务进程,并安全的保存起来。当系统晚一点想要重启Activity的时候, 它会把之前的Bundle对象传递回应用,并用来恢复Activity之前的状态。

所以为什么会抛出之前的异常呢?问题的根源在于Bundle这个对象仅仅是Activity在onSaveInstanceState()方法被调用那一刻的快照。这就意味着当你如果在onSaveInstanceState()之后再调用FragmentTransaction.commit()的话,由于这次Transaction没有被作为Activity状态的一部分来保存,自然也就丢失掉了。从用户的角度来说,Transaction的遗失就导致意外的UI状态丢失。那么为了维护良好的用户体验,Android系统会不惜一切代价的避免页面状态的丢失,所以在这种情况发生的时候就直接抛出了IllegalStateException。

什么时候产生?

首先,不同的平台产生的时机和概率是不一样的。

  • 老的机器(Honeycomb之前)发生的概率更小
  • 使用support包发生的概率大于使用原生的包

根本原因是从Honeycomb开始Activity的生命周期做了重大的改变。

  • < Honeycomb:Activity是在onPause()触发时被杀死,这样onSaveInstanceState()是在onPause()之前调用
  • >= Honeycomb:Activity是在onStop()触发时才被杀死,这样onSaveInstanceState()是在onPause()之前调用

这样导致的结果是,support包需要根据平台的不同做出不同的行为。

  • >= Honeycomb:每次在onSaveInstanceState()之后的commit()都会发生异常
  • < Honeycomb:由于onSaveInstanceState()发生的时间更早,则更容易产生state loss的错误

如何避免?

谨慎的在Activity的生命周期方法中调用transaction的commit方法

大多数应用只会在第一次调用onCreate()或者响应用户输入的时候去commit transaction, 这样不会有什么问题。但是, 当您把transaction放到其他Activity的生命周期方法中时,比如onActivityResult(), onStart() 和onResume(),事情就会变得有点复杂。

您不应该在FragmentActivity#onResume()中去commit transaction,因为在某些情况下,这个方法可能会在Activity状态恢复之前被调用。如果您的应用需要在onCreate()之外的其他生命周期方法中去commit transaction,那就请在 FragmentActivity#onResumeFragments()或者Activity#onPostResume()中去commit。这两个方法保证会在Activity恢复状态之后才会被调用,所以就能完全避免状态丢失的可能性。

避免在异步回调中去处理transaction

包括使用AsyncTask#onPostExecute()和 LoaderManager.LoaderCallbacks#onLoadFinished()方法等。

举个例子,有下面这样的操作:

  1. Activity执行一个AsyncTask
  2. 用户按下了Home键,导致Activity的onSaveInstanceState()和onStop()执行
  3. AsyncTask执行完成并且onPostExecute()被调用,其不知道Activity这个时候已经被stopped
  4. FragmentTransaction在onPostExecute()中被调用,异常被抛出!!!

好避免异常的方法是不要在异步回调中去commit transaction。

只在不得已的情况下,才使用commitAllowingStateLoss()

commit()和commitAllowingStateLoss()唯一的不同是,后者当状态丢失时,不会抛出异常。

通常由于存在状态丢失的可能性,您不会希望使用这个方法。更好的解决方法是,在您的应用中使用commit(),并保证在Activity的状态保存之前调用它,来获取更好的用户体验。除非状态丢失的可能性不能被避免,否则您不应该使用commitAllowingStateLoss()。

results matching ""

    No results matching ""