Android ShowDialog : IllegalStateException: Can not perform this action after onSaveInstanceState

来源:互联网 发布:知乎问题 编辑:程序博客网 时间:2024/05/22 04:47

项目中遇到报错如下:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState    at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1413)    at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1431)    at android.app.BackStackRecord.commitInternal(BackStackRecord.java:693)    at android.app.BackStackRecord.commit(BackStackRecord.java:669)    at android.app.DialogFragment.show(DialogFragment.java:230)    at com.zw.coolweather.sample.MainActivity$2.onPageFinished(MainActivity.java:139)    at com.android.webview.chromium.WebViewContentsClientAdapter.onPageFinished(WebViewContentsClientAdapter.java:515)    at org.chromium.android_webview.AwContentsClientCallbackHelper$MyHandler.handleMessage(AwContentsClientCallbackHelper.java:188)    at android.os.Handler.dispatchMessage(Handler.java:102)    at android.os.Looper.loop(Looper.java:148)    at android.app.ActivityThread.main(ActivityThread.java:5441)    at java.lang.reflect.Method.invoke(Native Method)    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)

报错的地方:定位于at android.app.DialogFragment.show(DialogFragment.java:230)

                    mainDialogFrag = new MainDialogFrag();                          Bundle bundle = new Bundle();                    bundle.putString("url", mainWebDataModel.url);                    bundle.putString("title", mainWebDataModel.title);                    bundle.putString("msgtype", mainWebDataModel.msgtype);                    mainDialogFrag.setArguments(bundle);                    mainDialogFrag.show(getFragmentManager(), "MainDialogFrag");

修改如下:

解决办法就是把commit()方法替换成 commitAllowingStateLoss()就行了。

                    mainDialogFrag = new MainDialogFrag();                    Bundle bundle = new Bundle();                    bundle.putString("url", mainWebDataModel.url);                    bundle.putString("title", mainWebDataModel.title);                    bundle.putString("msgtype", mainWebDataModel.msgtype);                    mainDialogFrag.setArguments(bundle);                    FragmentTransaction transaction = getFragmentManager().beginTransaction();                    transaction.add(mainDialogFrag, "mainDialogFrag");                    transaction.commitAllowingStateLoss();

看commitAllowingStateLoss源码:

  /**     * Like {@link #commit} but allows the commit to be executed after an     * activity's state is saved.  This is dangerous because the commit can     * be lost if the activity needs to later be restored from its state, so     * this should only be used for cases where it is okay for the UI state     * to change unexpectedly on the user.     */    public abstract int commitAllowingStateLoss();

看出错的show方法

 /**     * Display the dialog, adding the fragment to the given FragmentManager.  This     * is a convenience for explicitly creating a transaction, adding the     * fragment to it with the given tag, and committing it.  This does     * <em>not</em> add the transaction to the back stack.  When the fragment     * is dismissed, a new transaction will be executed to remove it from     * the activity.     * @param manager The FragmentManager this fragment will be added to.     * @param tag The tag for this fragment, as per     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.     */    public void show(FragmentManager manager, String tag) {        mDismissed = false;        mShownByMe = true;        FragmentTransaction ft = manager.beginTransaction();        ft.add(this, tag);        ft.commit();    }

在看commit方法

    /**     * Schedules a commit of this transaction.  The commit does     * not happen immediately; it will be scheduled as work on the main thread     * to be done the next time that thread is ready.     *     * <p class="note">A transaction can only be committed with this method     * prior to its containing activity saving its state.  If the commit is     * attempted after that point, an exception will be thrown.  This is     * because the state after the commit can be lost if the activity needs to     * be restored from its state.  See {@link #commitAllowingStateLoss()} for     * situations where it may be okay to lose the commit.</p>     *      * @return Returns the identifier of this transaction's back stack entry,     * if {@link #addToBackStack(String)} had been called.  Otherwise, returns     * a negative number.     */    public abstract int commit();

在使用Fragment的过程中,常常会遇到在Activity的onSaveInstanceState方法调用之后,操作commit或者popBackStack而导致的crash.
因为在onSaveInstanceState方法之后的操作状态可能会丢失,因此Android framework默认会抛出一个异常.
对于commit方法来说,单纯避免这个异常很简单,使用commitAllowingStateLoss方法即可.但是popBackStack以及 popBackStackImmediate也都会检查state(checkStateLoss),特别需要注意的是Activity的 onBackPressed方法

如果onBackPressed在onSavedInstanceState之后调用,那么就会crash.

onBackPressed的调用时机:

  • targetSdkVersion <= 5,在onKeyDown中调用
  • targetSdkVersion > 5,在onKeyUp中调用

onSavedInstanceState的调用时机(如果调用的话):

  • 一定在onStop之前
  • 可能在onPause之前,也可能在onPause与onStop之间

需要注意的是: onSavedInstanceState方法不一定会调用,只有在Activity因为某些原因而被Framework销毁,并且之后还需要重新创建的情况,才需要调用(例如:旋屏,或者内存不足而回收返回栈中的某些Activity)

举例:

  • Activity A在前台时,屏幕逐渐变暗直至锁屏,那么A的onSavedInstanceState会被调用
  • Activity A start Activity B,Activity A的onSavedInstanceState会被调用
  • Activity A因为返回键或者finish调用而返回到上一个界面,那么A的onSavedInstanceState不会被调用

因此,当onBackPressed在onSavedInstanceState方法之后调用,就一定会crash.解决方法主要有两种:

1、重写Activity的onSavedInstanceState()方法,并且注释掉super调用.

这种方法能避免crash,但是它会导致整个Activity的状态丢失.以DialogFragment为例,正常情况下,显示的 DialogFragment在旋屏Activity重新创建之后,不需要我们处理,Dialog会自动显示出来(参见 DialogFragment.onStart()),但是注释掉Activity的onSavedInstanceState()方法之 后,Fragment状态丢失,Activity重新创建之后,Dialog也就不会再显示出来了.

2、更好且通用的做法:在调用commit,popBackStack以及onBackPressed方法之前,判断 onSavedInstanceState()方法是否已经执行,并且onResume方法还没有执行,如果不是,那么直接操作,否则加入到 pending队列,等待onResumeFragments或者onPostResume之后再执行.

注意:不要在onResume中操作,因为这时候FragmentManager中的mStateSaved依然可能是true.(如果执行顺序是 onSavedInstanceState()->onPause()->onResume() 或者 onPause()->onSavedInstanceState()->onResume());

public void endPaintingPager(int index) {        if (mFirstLevel == PAINTING_PAGER) {            mFirstLevel = PAINTER_START;            if (!mIsStateSaved) {                getSupportFragmentManager().popBackStack();            } else {                mPopBackStackRunnable = new Runnable() {                    @Override                    public void run() {                        getSupportFragmentManager().popBackStack();                    }                };            }        }    }
@Override    protected void onPostResume() {        super.onPostResume();        if (mPopBackStackRunnable != null) {            mPopBackStackRunnable.run();        }    }

onSaveInstanceState

在activity的一个生命周期中,onSaveInstanceState()并非一定调用。
正如官网对该方法的解释所说:“This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state. ” 在“可能被系统杀死”之前调用。suo的很准确啊,先明白一点:如果一个activityA不可能在后台被系统主动kill掉。

onSaveInstanceState方法是Activity的成员方法,主要用于在Activity销毁时保存Activity相关的对象信息,而其执行的时机不是我们主动调用的,而是Android系统的framework帮忙调用的,而其调用的时机在onPause方法之后执行在onStop方法之前执行。

参考:Android源码解析(二十四)–>onSaveInstanceState执行时机

1、onSaveInstanceState方法是Activity的生命周期方法,主要用于在Activity销毁时保存一些信息。

2、当Activity只执行onPause方法时(Activity a打开一个透明Activity b)这时候如果App设置的targetVersion大于android3.0则不会执行onSaveInstanceState方法。

3、当Activity执行onStop方法时,通过分析源码我们知道调用onSaveInstanceState的方法直接传值为true,所以都会执行onSaveInstanceState方法。

参考:onSaveInstanceState(Bundle outState)的调用时机

  1. 只要Activity不finish,Activity进入后台(比如Home键,跳转到其他的Activity),则其就会调用onSaveInstanceState(Bundle outState)方法,而且这个方法是在onPause、onStop方法之间进行调用的。

  2. 如果Activity是执行了finish方法,才进入的后台,则不调用这个onSaveInstanceState(Bundle outState),而且下次再进入时,也不会使用这个保存的数据。

  3. 在系统杀掉Activity所在的进程时,onSaveInstanceState(Bundle outState)方法根本就没有调用过。

总结:系统在杀进程时,不可能有时间去执行多余的代码,也只有这种方法,才能保存Activity里的最新数据,所以onSaveInstanceState(Bundle outState)会被执行多次,并不是只有一次。

参考:activity中onSaveInstanceState方法调用时机详解

onResume() 不会被杀。前台应用,系统是不会主动kill的。
onPause() HONEYCOMB(android3.0)之前,可能被杀;3.0之后不会被杀。
onStop() 可能被杀。

明确activity可能在生命周期中被杀的方法之后,根据上边说明便可知:

android3.0之前:

onResume() -- [optional]onSaveInstanceState() -- onPause(),

即调用onPause()之前,可能调用onSaveInstanceState()

android3.0之后:

onResume() -- onPause() -- [optional]onSaveInstanceState() -- onStop(),

即调用onStop()之前,可能调用onSaveInstanceState()

后台的activity被系统主动kill掉才会在它的onPause、onStop之间调用,此时还没被finish(onDestroy)。

如果一个activityA不可能在后台被系统主动kill掉,那么就不会调用该方法。
比如一下逻辑:
activityA.startActivity(activityB)
activityA.finish()
A启动了B,但是A自己把自己finish了,也就是说系统不可能主动kill activityA了,因此虽然A的onPause()、onStop()被调用,onSaveInstanceState()方法也是不会调用到的。
那么同理,默认情况下在一个activity中,返回退出也是不会调用onSaveInstanceState()的。

参考:

[DialogFragment - Can not perform this action after onSaveInstanceState

](https://stackoverflow.com/questions/21634767/dialogfragment-can-not-perform-this-action-after-onsaveinstancestate)

Android ShowDialog : IllegalStateException: Can not perform this action after onSaveInstanceState

解决java.lang.IllegalStateException: Can not perform this action after onSaveInstance

阅读全文
0 0
原创粉丝点击