获取exception“IllegalStateException:不能执行此操作后onSaveInstanceState”
我有一个活的Android应用程序,从市场上我收到以下堆栈跟踪,我不知道为什么它发生的应用程序代码中没有发生,但它由应用程序中的某些或其他事件引起(假设)
我不使用片段,仍然有一个FragmentManager的参考。 如果任何机构可以对一些隐藏的事实提出一些疑问,以避免这种types的问题:
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)
这是迄今为止我遇到的最愚蠢的错误。 我有一个Fragment
应用程序完美的API <11 , Force Closing
API> 11 。
我真的不知道在调用saveInstance
的Activity
生命周期中他们改变了什么,但我在这里是如何解决这个问题的:
@Override protected void onSaveInstanceState(Bundle outState) { //No call for super(). Bug on API Level > 11. }
我只是不打电话给.super()
,一切都很好。 我希望这会为你节省一些时间。
编辑:经过一些更多的研究,这是支持包中的一个已知的错误 。
如果你需要保存实例,并添加一些你的outState
Bundle
你可以使用下面的代码:
@Override protected void onSaveInstanceState(Bundle outState) { outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE"); super.onSaveInstanceState(outState); }
EDIT2:这也可能发生,如果你想在你的Activity
在后台消失后执行一个事务。 为了避免这种情况,你应该使用commitAllowingStateLoss()
编辑3:上述解决scheme是从我能记得的早期的support.v4库中解决问题。 但如果你仍然有这个问题,你还必须阅读@AlexLockwood的博客: 片段交易和活动状态损失
来自博客文章的摘要(但我强烈build议您阅读它):
- 不要在pre-Honeycomb的
onPause()
之后commit()
事务,而在Honeycomb之后不要commit()
事务 - 在
Activity
生命周期方法中提交事务时要小心。 使用onCreate()
,onResumeFragments()
和onPostResume()
- 避免在asynchronouscallback方法中执行事务
- 只使用
commitAllowingStateLoss()
作为最后的手段
在Android源代码中查看导致此问题的原因是,在FragmentManagerImpl
类(Activity中可用的实例)中的标记mStateSaved的值为true。 当从Activity#onSaveInstanceState
调用后退堆栈(saveAllState)时,它被设置为true。 之后,来自ActivityThread的调用不会使用FragmentManagerImpl#noteStateNotSaved()
和dispatch()
可用重置方法来重置此标志。
我看到它的方式有一些可用的修复,这取决于你的应用程序在做什么和使用:
好方法
在别的之前:我会宣传Alex Lockwood的文章 。 那么,从我到目前为止所做的:
-
对于不需要保存任何状态信息的片段和活动,请调用commitAllowStateLoss 。 从文档中获取:
允许在活动状态保存后执行提交。 这是很危险的,因为如果活动需要稍后从其状态恢复,则提交可能会丢失,所以这应该只用于UI状态在用户意外改变的情况。 我想这是可以使用,如果片段显示只读信息。 或者,即使他们显示可编辑的信息,使用callback方法来保留编辑的信息。
-
在事务提交之后(您只需调用
commit()
),就调用FragmentManager.executePendingTransactions()
。
不build议的方式:
-
正如上面提到的Ovidiu Latcu,不要调用
super.onSaveInstanceState()
。 但是这意味着你将失去你的活动的整个状态以及碎片状态。 -
覆盖
onBackPressed
并在那里只调用finish()
。 如果你的应用程序不使用Fragments API,这应该是OK的; 在super.onBackPressed
有一个调用FragmentManager#popBackStackImmediate()
。 -
如果您同时使用Fragments API和您的活动状态是重要的/重要的,那么您可以尝试使用reflectionAPI
FragmentManagerImpl#noteStateNotSaved()
调用。 但是这是一个黑客,或者可以说这是一个解决方法。 我不喜欢它,但在我的情况下,它是完全可以接受的,因为我有一个使用弃用代码(TabActivity
和隐式LocalActivityManager
)的遗留应用程序的代码。
以下是使用reflection的代码:
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); invokeFragmentManagerNoteStateNotSaved(); } @SuppressWarnings({ "rawtypes", "unchecked" }) private void invokeFragmentManagerNoteStateNotSaved() { /** * For post-Honeycomb devices */ if (Build.VERSION.SDK_INT < 11) { return; } try { Class cls = getClass(); do { cls = cls.getSuperclass(); } while (!"Activity".equals(cls.getSimpleName())); Field fragmentMgrField = cls.getDeclaredField("mFragments"); fragmentMgrField.setAccessible(true); Object fragmentMgr = fragmentMgrField.get(this); cls = fragmentMgr.getClass(); Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {}); noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {}); Log.d("DLOutState", "Successful call for noteStateNotSaved!!!"); } catch (Exception ex) { Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex); } }
干杯!
如果您在片段活动的onSaveInstanceState()
被调用后尝试执行片段转换,则会发生此类exception。
这种情况发生的一个原因是,当一个活动停止的时候,你离开了一个AsyncTask
(或者Thread
)。
调用onSaveInstanceState()
之后的任何转换可能会在系统回收资源活动并稍后重新创build时丢失。
在调用super.onPostResume()之后,只需在显示片段之前调用super.onPostResume()或在onPostResume()方法中移动代码即可。 这解决了问题!
在屏幕被locking\消隐并且Activity +对话框的实例状态已被保存之后,在对话框片段上调用dismiss()
时也会发生这种情况。 为了解决这个问题:
dismissAllowingStateLoss()
从字面上来看,我每次都会抛开一个对话,我不再关心它的状态,所以这样做可以,事实上你并没有失去任何状态。
简短和工作解决scheme:
遵循简单的步骤:
第1步 :覆盖相应片段中的onSaveInstanceState状态。 并从中删除超级方法。
@Override public void onSaveInstanceState(Bundle outState) { };
第2步 :使用CommitAllowingStateLoss(); 而不是commit(); 而片段操作。
fragmentTransaction.commitAllowingStateLoss();
这工作对我来说…发现了我自己…希望它可以帮助你!
1)没有全局的“静态”FragmentManager / FragmentTransaction。
2)onCreate,总是重新初始化FragmentManager!
样品如下:
public abstract class FragmentController extends AnotherActivity{ protected FragmentManager fragmentManager; protected FragmentTransaction fragmentTransaction; protected Bundle mSavedInstanceState; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSavedInstanceState = savedInstanceState; setDefaultFragments(); } protected void setDefaultFragments() { fragmentManager = getSupportFragmentManager(); //check if on orientation change.. do not re-add fragments! if(mSavedInstanceState == null) { //instantiate the fragment manager fragmentTransaction = fragmentManager.beginTransaction(); //the navigation fragments NavigationFragment navFrag = new NavigationFragment(); ToolbarFragment toolFrag = new ToolbarFragment(); fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag"); fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag"); fragmentTransaction.commitAllowingStateLoss(); //add own fragment to the nav (abstract method) setOwnFragment(); } }
当我试图在onActivityForResult()方法中显示片段时,我总是得到这个,所以接下来的问题是:
- 我的活动暂停并停止,这意味着onSaveInstanceState()已被调用(对于蜂窝前和蜂窝后设备)。
- 在任何结果的情况下,我做交易显示/隐藏片段,这导致此IllegalStateException。
我所做的是接下来:
- 增加值来确定我想要的操作是否完成(例如,从camere拍照 – isPhotoTaken) – 它可以是布尔值或整数值,取决于您需要多less不同的事务。
- 在覆盖onResumeFragments()方法我检查了我的价值,并做了片段交易后,我需要。 在这种情况下,commit()在onSaveInstanceState之后没有完成,因为状态是在onResumeFragments()方法中返回的。
我用onconfigurationchanged解决了这个问题。 诀窍是,根据android的活动生命周期,当你明确地调用意图(相机意图,或任何其他); 在这种情况下活动被暂停并且onsavedInstance被调用。 将设备旋转到活动活动期间以外的其他位置时, 做片段操作,如片段提交导致非法状态exception。 有很多抱怨。 这是关于android活动生命周期pipe理和正确的方法调用。 为了解决这个问题,我这样做了:1-覆盖你的活动的onsavedInstance方法,并确定当前的屏幕方向(纵向或横向),然后在你的活动暂停之前设置你的屏幕方向。 这样的活动locking你的活动的屏幕旋转,以防被另一个旋转。 2 – 然后,重写onresume活动方法,并将您的方向模式设置为传感器,以便在调用onsaved方法后,将再调用一次onconfiguration来正确处理旋转。
您可以将此代码复制/粘贴到您的活动中处理:
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show(); int orientation =this.getDisplayOrientation(); //Lock the screen orientation to the current display orientation : Landscape or Potrait this.setRequestedOrientation(orientation); } //A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device public int getDisplayOrientation() { Display getOrient = getWindowManager().getDefaultDisplay(); int orientation = getOrient.getOrientation(); // Sometimes you may get undefined orientation Value is 0 // simple logic solves the problem compare the screen // X,Y Co-ordinates and determine the Orientation in such cases if (orientation == Configuration.ORIENTATION_UNDEFINED) { Configuration config = getResources().getConfiguration(); orientation = config.orientation; if (orientation == Configuration.ORIENTATION_UNDEFINED) { // if height and widht of screen are equal then // it is square orientation if (getOrient.getWidth() == getOrient.getHeight()) { orientation = Configuration.ORIENTATION_SQUARE; } else { //if widht is less than height than it is portrait if (getOrient.getWidth() < getOrient.getHeight()) { orientation = Configuration.ORIENTATION_PORTRAIT; } else { // if it is not any of the above it will defineitly be landscape orientation = Configuration.ORIENTATION_LANDSCAPE; } } } } return orientation; // return value 1 is portrait and 2 is Landscape Mode } @Override public void onResume() { super.onResume(); Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show(); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); }
我有同样的问题,得到IllegalStateException,但用commitAllowingStateLoss()取代我所有的调用commit()没有帮助。
罪魁祸首是DialogFragment.show()的调用。
我围绕着它
try { dialog.show(transaction, "blah blah"); } catch(IllegalStateException e) { return; }
那是做的。 好吧,我不能显示对话框,但在这种情况下,这很好。
这是我的应用程序中我第一次调用FragmentManager.beginTransaction()但从来没有调用commit()的唯一地方,所以当我查找“commit()”时没有find它。
有趣的是,用户从不离开应用程序。 相反,杀手是一个AdMob插页式广告。
我对这个问题的解决scheme是
在片段添加方法中:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ... guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap); guideMap = guideMapFragment.getMap(); ... } @Override public void onDestroyView() { SherlockFragmentActivity a = getSherlockActivity(); if (a != null && guideMapFragment != null) { try { Log.i(LOGTAG, "Removing map fragment"); a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit(); guideMapFragment = null; } catch(IllegalStateException e) { Log.i(LOGTAG, "IllegalStateException on exit"); } } super.onDestroyView(); }
可能是坏的,但找不到更好的东西。
如果用户旋转屏幕,onSaveInstance将被调用,以便它可以加载与新的方向相关的资源。
用户可能会旋转屏幕,然后按下后退button(因为用户也可能在使用应用程序时摸索手机)
我得到这个问题。但我认为这个问题是不相关的提交和commitAllowStateLoss。
以下堆栈跟踪和exception消息是关于commit()的。
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)
但是这个exception是由onBackPressed()
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source) at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(Unknown Source) at android.support.v4.app.FragmentActivity.onBackPressed(Unknown Source)
它们都是由checkStateLoss()引起的
private void checkStateLoss() { if (mStateSaved) { throw new IllegalStateException( "Can not perform this action after onSaveInstanceState"); } if (mNoTransactionsBecause != null) { throw new IllegalStateException( "Can not perform this action inside of " + mNoTransactionsBecause); }
在onSaveInstanceState之后,mStateSaved将为true。
这个问题很less发生。我从来没有遇到这个问题。我不能再次发生这个问题。
我发现问题25517
它可能发生在以下情况
-
后退键在onSaveInstanceState之后被调用,但在新活动启动之前。
-
在代码中使用onStop()
我不确定问题的根源是什么。 所以我用了一个丑陋的方式。
@Override public void onBackPressed() { try{ super.onBackPressed(); }catch (IllegalStateException e){ // can output some information here finish(); } }
我的应用程序中有同样的问题。 我已经解决了这个问题,只是调用super.onBackPressed();
在前一个类上,并使用该片段调用当前类的commitAllowingStateLoss()
。
阅读http://chris-alexander.co.uk/on-engineering/dev/android-fragments-within-fragments/
文章。 fragment.isResumed()检查帮助我在onDestroyView w / o使用onSaveInstanceState方法。
同样的问题,从我和经过一天的所有文章,博客和计算器我已经find了一个简单的解决scheme分析。 根本不要使用savedInstanceState,这是一行代码的情况。 在片段代码上:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(null); .....
这在Android 4.2以及支持库的源代码中得到了解决。
有关原因(和解决方法)的详细信息,请参阅Google错误报告: http : //code.google.com/p/android/issues/detail?id = 19917
如果你使用的支持库,那么你不应该担心这个bug(很长)[*]。 但是,如果您直接使用API(即不使用支持库的FragmentManager)并针对Android 4.2以下的API,那么您将需要尝试其中一种解决方法。
在编写本书的时候,Android SDK Manager仍然在分发显示这个bug的旧版本。
编辑我会在这里添加一些澄清,因为我明显地以某种方式混淆谁投票答复这个答案。
有几种不同的(但相关的)情况会导致抛出exception 。 我上面的回答是指在问题中讨论的具体实例,即Android中的一个错误,随后被修复。 如果因为另一个原因而得到这个exception,那是因为你不应该添加/删除片段(片段状态被保存之后)。 如果你遇到这种情况,那么可能是“ 嵌套片段 – IllegalStateException”在onSaveInstanceState“ ” 之后不能执行这个动作对你来说可能是有用的。
那么,尝试所有上述解决scheme后,没有成功(因为基本上我没有交易)。
在我的情况下,我使用AlertDialogs和ProgressDialog作为片段,有时,在旋转时,当要求FragmentManager时,错误上升。
我发现一个解决方法混合了一些类似的post:
它的3步解决scheme,全部在你的FragmentActivity上完成(在这种情况下,它被称为GenericActivity):
private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3 @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); //To avoid bug for fragments: Step 2 of 3 activity = new WeakReference<GenericActivity>(this); } @Override public FragmentManager getSupportFragmentManager(){ //To avoid bug for fragments: Step 3 of 3 if (this == activity.get()) { return super.getSupportFragmentManager(); } return activity.get().getSupportFragmentManager(); }
当我在一个片段中使用startactivity,我会得到这个exception;
当我改变使用startactivityforresult,例外是没有了:)
所以解决这个问题的简单方法是使用startActivityForResult api 🙂
当我按下button取消我的地图片段活动上的意图select器时,我得到这个exception。 我通过将onResume()(在我正在初始化片段并提交事务)的代码replace为onStart()来解决此问题,现在应用程序正常工作。 希望它有帮助。
你可以在popBackStackImmediate之前使用FragmentActivity.onStart
喜欢这个:
public void backStackFragment() { this.start(); getFragmentManager().popBackStackImmediate(); } public void start(){ FragmentActivity a = getActivity(); if(a instanceof DepositPlanPadActivity){ ((DepositPlanPadActivity)a).onStart(); } if(a instanceof SmallChangePlanPad){ ((SmallChangePlanPad)a).onStart(); } if(a instanceof UserCenterActivity){ ((UserCenterActivity)a).onStart(); } }
http://jorryliu.blogspot.com/2014/09/illegalstateexception-can-not-perform.html
经过研究,这个问题的解决办法是做你的片段在onresume提交。
资料来源: https : //wenchaojames.wordpress.com/2013/01/12/illegalstateexception-from-onactivityresult/
我的用例:我已经用片段中的监听器来通知活动发生了一些事情。 我在callback方法上做了新的片段提交。 这在第一次完美地工作。 但是在方向改变时,活动被保存的实例状态重新创build。 在这种情况下,片段不会再创build意味着该片段具有旧的销毁活动的监听器。 任何callback方法都会被触发。 它导致了导致问题的被破坏的活动。 解决scheme是用当前的实时活动重置监听器。 这解决了这个问题。
我发现,如果另一个应用程序是对话框types,并允许触摸发送到后台应用程序,那么几乎所有的后台应用程序将崩溃与此错误。 我想我们需要检查每次执行事务时是否保存或恢复实例。
在我的情况下,与相同的错误exception,我把“onBackPressed()”在一个可运行的(你可以使用任何视图):
myView.post(new Runnable() { @Override public void run() { onBackPressed() } });
我不明白为什么,但它的作品!
您可能正在调用fragmentManager.popBackStackImmediate(); 当活动暂停时。 活动没有完成,但暂停,而不是在前台。 你需要在popBackStackImmediate()之前检查活动是否暂停。
谢谢@ gunar,但我认为有一个更好的方法。
根据doc:
* If you are committing a single transaction that does not modify the * fragment back stack, strongly consider using * {@link FragmentTransaction#commitNow()} instead. This can help avoid * unwanted side effects when other code in your app has pending committed * transactions that expect different timing. * * @return Returns true if there were any pending transactions to be * executed. */ public abstract boolean executePendingTransactions();
所以使用commitNow
来replace:
fragmentTransaction.commit(); FragmentManager.executePendingTransactions()
这种情况发生在你尝试加载一个片段时,但是这个活动已经改变了它的状态到onPause(),例如当你试图获取数据并将其加载到活动中,但是到用户点击某个button时转移到下一个活动。
你可以用两种方法解决这个问题
您可以使用transaction.commitAllowingStateLoss()而不是transaction.commit()加载片段,但最终可能会丢失已完成的提交操作。
要么
确保活动处于恢复状态,加载片段时不会暂停。 创build一个布尔值,并检查活动是否不会onPause()状态。
@Override public void onResume() { super.onResume(); mIsResumed = true; } @Override public void onPause() { mIsResumed = false; super.onPause(); }
然后在加载片段时检查是否存在活动,只在活动处于前台时加载。
if(mIsResumed){ //load the fragment }
我认为现在调用FragmentActivity.onStateNotSaved()
之前这些操作可能是最好的select。