项目中遇到报错如下:
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)的调用时机
只要Activity不finish,Activity进入后台(比如Home键,跳转到其他的Activity),则其就会调用onSaveInstanceState(Bundle outState)方法,而且这个方法是在onPause、onStop方法之间进行调用的。
如果Activity是执行了finish方法,才进入的后台,则不调用这个onSaveInstanceState(Bundle outState),而且下次再进入时,也不会使用这个保存的数据。
在系统杀掉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
Android ShowDialog : IllegalStateException: Can not perform this action after onSaveInstanceState
解决java.lang.IllegalStateException: Can not perform this action after onSaveInstance