Android error:Can not perform this action after onSaveInstanceState

最近在项目中发现了这个bug,不是每次都发生,偶然会出现的,抓取的log日志:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)

google了好久,才知道这个bug出现的场景是:一个Activity调用了onsaveInstance方法之后,又进行了更新view或者提交了fragment(不要在onsaveInstance之后调用transaction的commit方法)。关于onsaveInstance方法调用的时机,可以参考我的Android中的onSaveInstanceState和onRestoreInstanceState()

而且,由于Honeycomb(3.0)做了更改,在3.0之前onpause方法之前有可能会调用onsaveinstancestate方法,在3.0之后在onStop方法之前有可能会调用onsaveinstancestate方法,这两个有什么区别呢?也就是说,你在onpause和onstop方法中调用了一个提交的方法(比如commit方法),在3.0之前就会出现状态丢失(STATE LOSS)的bug了。

解决方法:

@Override
protected void onSaveInstanceState(Bundle outState) {
    //No call for super().
}
默认是调用super的 saveInstanceState方法的

stackoverflow上面说这是android的一个bug,暂时也只能这样处理了,不过对于fragment,也可以用

transaction.commitAllowingStateLoss();来进行提交,不过不过万不得已最好不要用这个,自己掌握生命周期岂不是更好。

总结一下,为了避免出现这种bug,请记住以下几点:

1.在honeycomb(3.0)之前,调过onPause方法之后,不要提交(commit),在3.0之后,调过onStop方法之后,不要提交(commit)

2.在activity的生命周期提交transaction的时候要注意,在oncreate(),onResumtFragemnts(),onPostResume()方法来提交

3.避免在异步线程的回调里面执行transaction。

4.最后没办法的情况下才用commitAllowingStateLoss()。

参考:http://stackoverflow.com/questions/7469082/getting-exception-illegalstateexception-can-not-perform-this-action-after-onsa

不过我上面只是简单的说了下,总结了一下,具体的详细说明在下面:

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

这是国外的一个网址,看不到的要翻墙,下面我简单对内容做了一下整理。

 

异常出现的原因

异常出现的原因:在Activity的状态保存之后,调用FragmentTransaction的commit(),因此可能会造成Activity state loss (Activity状态丢失)的现象。在我们深入了解它之前,首先看一下onSaveInstanceState()这个方法。由于Android应用程序并不能控制其自身的生命周期,其生命周期由Android系统根据用户操作、系统状态等来控制,Android系统可以决定终止某个进程(应用程序)来回收内存资源,而处于后台(Background)的应用程序可能会被终止。然而作为用户,当其在对App1进行了某些操作(比如输入文字)之后,切换到了App2,此时如果Android系统决定终止App1,而之前输入的内容没有被保存下来的话,就会造成用户信息的丢失,这是一种不好的用户体验。为了让Android系统对应用程序的管理对用户透明,在应用程序被(自愿)回收之前,可以在onSaveInstanceState()方法中保存其当前Activity的状态。这种情况下,就算App1被Android系统终止,当下次用户再打开此Activity之后,这些被保存的状态也可以重新加载,这样Ativity是否被Android系统回收对于用户来说就没有区别了,切换效果会和App1在foreground和background状态切换一样。

onSaveInstanceState()方法有Bundle参数,其包含了Activity中dialog、fragment、view的状态,当这个方法返回时,系统会将其通过Binder接口传到System Servier进程中,并在系统中保存。当Android系统决定重新创建该Activity,系统会将这个Bundle对象传回,并在新创建的Activity中显示Bundle的数据。

IllegalStateException出现的原因和onSaveInstanceState()有很大关系,应用程序的状态只会被该方法的参数中的Bundle对象保存下来,也就是说,如果FragmentTrasaction.commit()方法如果在onSaveInstanceState()之后被调用,那么这个transaction将不会被作为Activity的状态而保存下来。从用户的视角来说,他们会认为这个transaction丢失了,造成了UI的状态丢失。为了提高用户体验,Android系统在在遇到状态丢失这种情况时,会抛出IllegalStateException异常。

异常出现的地点

如果你以前遇到过这种异常,那么可能你会注意到它在不同Android版本发生的地点是不同的。比如,你可能注意到在老的机器中,它发生的几率更低,或者使用support lib时,其发生的几率更大。这种不一致造成了一个观点是support lib存在bug,但事实并非如此。

这种不一致的原因是Android Activity的生命周期在Honeycomb(Android 3.0)的时候发生了变化:

Honeycomb之前,Activity必须在pause之后才能被kill掉,意味着onSaveInstanceState()方法会恰好在onPause()方法之前(紧邻着)被调用;

honeycomb以及之后的版本,Activity需要在stop之后才能被kill掉,所以onSaveInstanceState()会在onStop之前调用。

image

因为Android Activity生命周期的更改,support lib会根据不同版本提供不同的功能。比如在Honeycomb之后的版本,当commit()在onSaveInstanceState()之后被调用,则这个异常会被抛出。然而,因为在Honeycomb之前的Android版本中,onStateInstanceState()被调用处在Activity生命周期更早的地方,如果适用于同样的异常抛出规则,则这个异常会被频繁抛出,不利于编程,在权衡了程序编写和用户体验之后,Android做了一个妥协:处在onPause()和onStop()之间的commit()将不会抛出异常,但是会有Activity状态丢失的后果。

image

 

避免异常的建议

以上介绍了IllegalStateException出现的原因和地点,重要的是理解support lib的工作方式,以及避免状态丢失的重要性。下面是关于如何尽量避免该异常的建议:

  • 在Activity生命周期方法中commit transaction的时候要小心。

需要尽量避免在某个Activity生命周期的某个方法中进行Transaction的commit()操作。当然首次调用onCreate()方法例外,此时bundle为null。如果应用程序中只在首次onCreate()中调用了Transaction的commit(),可以保证永远不会出现IllegalStateException。然而,如果commit操作是在onActivityResult()、onStart()、或者onResume()中被调用的,就可能会出现问题。比如,如果你在onResume()方法中调用commit。根据Android官方文档(参考文档):

protected void onResume ()

Dispatch onResume() to fragments. Note that for better inter-operation with older versions of the platform, at the point of this call the fragments attached to the activity are not resumed. This means that in some cases the previous state may still be saved, not allowing fragment transactions that modify the state. To correctly interact with fragments in their proper state, you should instead override onResumeFragments().

FragmentActivity的onResume方法不能保证某个Fragment已经resume,造成了保存的还是原来的状态,所以这里不允许commit的操作。

当你需要Transaction的commit操作的时候,可以使用FragmentActivity的onResumeFragments()或者Activity的onPostResume()方法。Android系统保证了这两个方法会在Activity的状态恢复之后才被调用,也就会避免了可能的状态丢失。

举个例子说明(例子:点击打开链接)如何使用onPostResume()方法来避免onActivityResult()中进行commit:

这里使用的是一个标志mReturningWithResult,默认为false,当有满足条件的返回时,将其设置为true,不做commit操作,而是将commit操作转入onPostResume()方法中,根据mReturningWithResult决定是否进行commit操作。

private boolean mReturningWithResult = false;

@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mReturningWithResult = true;}

@Overrideprotected void onPostResume() {
    super.onPostResume();
    if (mReturningWithResult) {
        // Commit your transactions here.
    }
    // Reset the boolean flag back to false for next time.
    mReturningWithResult = false;}

因为Android系统保证了onResume()会在onAcitivtyResult之后被调用,而顾名思义onPostResume()在onResume()之后调用,并且这时候Activity的状态(包含内部Fragment的状态)都已经恢复,所以这时候做的commit操作可以避免状态丢失。

  • 避免异步回调方法内部执行transaction。

    包括常用的AsyncTask#onPostExecute()和LoaderManager.LoaderCallback#onLoadFinished()。在这些方法中执行transaction的问题是,在他们被调用时不知道Activity生命周期的当前状态。例如,考虑下面两个事件的顺序:

  1. Activity执行一个AsyncTask
  2. 用户按下“Home”键,onSaveInstanceState()和onStop()被调用。
  3. AsyncTask执行完毕,onPostExecute()被调用,不知道Activity已经停止。
  4. onPostExecute()中的一个FragmentTransaction被commit,导致异常抛出。

    通常,避免这个异常的最好的方法是,避免在异步回调方法中commit transaction。谷歌工程师似乎也同意这种观点。发表在Android Developers group上的这篇文章中,Android团队认为那些因在异步回调方法中commit FragmentTransaction而导致的UI切换产生了不好的用户体验。如果你的应用需要在回调方法中执行transaction并且没有简单地方法保证紧跟着onSaveInstanceState()调用callback,那么你需要借助commitAllowingStateLoss(),并且处理可能出现的状态丢失(查看StackOverflow上的这两篇文章来获取更多提示,文章一文章二

  • 把commitAllowingStateLoss()放到最后考虑。

     调用commit()和commitAllowingStateLoss()的唯一不同点是,如果出现状态丢失,后者不会抛出异常。通常你不会想使用这种方法,因为它意味着状态丢失的可能。当然,更好的办法是在你的应用程序中保证在Activity状态保存之前调用commit(),这样才会有好的用户体验。除非不能避免状态丢失,否则不应该使用commitAllowingStateLoss()

  参考:http://blog.csdn.net/welovesunflower/article/details/42806235

http://blog.csdn.net/TorrenceLiu/article/details/39212659

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值