IllegalStateException:使用ViewPager执行onSaveInstanceState后无法执行此操作

我从市场上的应用程序获取用户报告,提供以下例外情况:

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.onKeyUp(Activity.java:2044) at android.view.KeyEvent.dispatch(KeyEvent.java:2529) at android.app.Activity.dispatchKeyEvent(Activity.java:2274) at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803) 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:1855) at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277) at android.app.Activity.dispatchKeyEvent(Activity.java:2269) at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803) 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.widget.TabHost.dispatchKeyEvent(TabHost.java:297) 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:1855) at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277) at android.app.Activity.dispatchKeyEvent(Activity.java:2269) at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803) at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2880) at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2853) at android.view.ViewRoot.handleMessage(ViewRoot.java:2028) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:132) at android.app.ActivityThread.main(ActivityThread.java:4028) 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:844) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602) at dalvik.system.NativeStart.main(Native Method) 

显然这与FragmentManager有关,我不使用它。 stacktrace不显示任何我自己的类,所以我不知道发生这种exception,以及如何防止它。

logging:我有一个tabhost,并在每个选项卡中有一个ActivityGroup活动之间切换。

请在这里查看我的答案。 基本上我只是:

 @Override protected void onSaveInstanceState(Bundle outState) { //No call for super(). Bug on API Level > 11. } 

不要在saveInstanceState方法上调用super() 。 这是搞砸了…

这是支持包中的一个已知错误 。

如果你需要保存实例并添加一些你的outState Bundle你可以使用下面的代码:

 @Override protected void onSaveInstanceState(Bundle outState) { outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE"); super.onSaveInstanceState(outState); } 

最后,正确的解决scheme是(如评论所示)使用:

 transaction.commitAllowingStateLoss(); 

当添加或执行导致ExceptionFragmentTransaction

有类似的错误信息有许多相关的问题。 检查这个特定的堆栈跟踪的第二行。 这个exception特别与调用FragmentManagerImpl.popBackStackImmediate

如果会话状态已保存,则此方法调用(如popBackStack )将总是失败,并显示IllegalArgumentException 。 检查来源。 没有任何事情可以阻止这个exception被抛出。

  • 移除对super.onSaveInstanceState的调用将无济于事。
  • 使用commitAllowingStateLoss创build碎片将无济于事。

以下是我如何观察问题:

  • 有一个提交button的表单。
  • 当点击button时,会创build一个对话框并开始一个asynchronous过程。
  • 用户在完成处理之前单击home键 – onSaveInstanceState被调用。
  • 该过程完成后,将进行callback并尝试popBackStackImmediate
  • IllegalStateException被抛出。

以下是我所做的解决方法:

由于无法避免callback中的IllegalStateException ,因此请忽略它。

 try { activity.getSupportFragmentManager().popBackStackImmediate(name); } catch (IllegalStateException ignored) { // There's no way to avoid getting this if saveInstanceState has already been called. } 

这足以阻止应用程序崩溃。 但现在用户将恢复应用程序,并看到他们认为他们按下的button没有被按下(他们认为)。 表单片段仍在显示!

为了解决这个问题,当创build对话框时,使一些状态指示进程已经开始。

 progressDialog.show(fragmentManager, TAG); submitPressed = true; 

并将这个状态保存在包中。

 @Override public void onSaveInstanceState(Bundle outState) { ... outState.putBoolean(SUBMIT_PRESSED, submitPressed); } 

不要忘记在onViewCreated再次加载它

然后,在恢复时,如果先前尝试提交,则回滚碎片。 这可以防止用户回到看起来像未提交的表单。

 @Override public void onResume() { super.onResume(); if (submitPressed) { // no need to try-catch this, because we are not in a callback activity.getSupportFragmentManager().popBackStackImmediate(name); } } 

在显示片段之前检查活动是否为commitAllowingStateLoss()并注意commitAllowingStateLoss()

例:

 if(!isFinishing()) { FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); DummyFragment dummyFragment = DummyFragment.newInstance(); ft.add(R.id.dummy_fragment_layout, dummyFragment); ft.commitAllowingStateLoss(); } 

这是对这个问题的一个不同的解决scheme。

使用私有成员variables,您可以将返回的数据设置为可以在super.onResume()之后处理的意图。

像这样:

 private Intent mOnActivityResultIntent = null; @Override protected void onResume() { super.onResume(); if(mOnActivityResultIntent != null){ ... do things ... mOnActivityResultIntent = null; } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data){ if(data != null){ mOnActivityResultIntent = data; } } 

简短和工作解决scheme:

遵循简单的步骤

脚步

第1步:覆盖相应片段中的onSaveInstanceState状态。 并从中删除超级方法。

  @Override public void onSaveInstanceState( Bundle outState ) { } 

第2步:使用fragmentTransaction.commitAllowingStateLoss( );

而不是fragmentTransaction.commit( ); 而片段操作。

注意 ,使用transaction.commitAllowingStateLoss()可能会给用户带来不好的体验。 有关为什么引发此exception的更多信息,请参阅此帖 。

我发现这种问题肮脏的解决scheme。 如果你仍然想保持你的ActivityGroups出于任何原因(我有时间限制的原因),你只是实现

 public void onBackPressed() {} 

在你的Activity做一些back代码。 即使在较旧的设备上没有这样的方法,这个方法也会被较新的方法调用。

不要使用commitAllowingStateLoss(),它只能用于UI状态在用户意外改变的情况。

https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss();

相反,使用if(fragment.isResume())检查外部操作遇到此IllegalStateException “不能执行此操作后onSaveInstanceState”

我有类似的问题,情况是这样的:

  • 我的活动是添加/replace列表片段。
  • 每个列表片段都有对活动的引用,以便在列表项被点击时通知活动(观察者模式)。
  • 每个列表片段调用setRetainInstance(true); 在其onCreate方法中。

活动的onCreate方法是这样的:

 mMainFragment = (SelectionFragment) getSupportFragmentManager() .findFragmentByTag(MAIN_FRAGMENT_TAG); if (mMainFragment == null) { mMainFragment = new SelectionFragment(); mMainFragment.setListAdapter(new ArrayAdapter<String>(this, R.layout.item_main_menu, getResources().getStringArray( R.array.main_menu))); mMainFragment.setOnSelectionChangedListener(this); FragmentTransaction transaction = getSupportFragmentManager() .beginTransaction(); transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG); transaction.commit(); } 

引发exception的原因是configuration更改(设备旋转),创build活动,从片段pipe理器的历史logging中检索主片段,同时该片段已经具有对已销毁活动引用

改变这个实现解决了这个问题:

 mMainFragment = (SelectionFragment) getSupportFragmentManager() .findFragmentByTag(MAIN_FRAGMENT_TAG); if (mMainFragment == null) { mMainFragment = new SelectionFragment(); mMainFragment.setListAdapter(new ArrayAdapter<String>(this, R.layout.item_main_menu, getResources().getStringArray( R.array.main_menu))); FragmentTransaction transaction = getSupportFragmentManager() .beginTransaction(); transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG); transaction.commit(); } mMainFragment.setOnSelectionChangedListener(this); 

您需要在每次创build活动时设置您的侦听器,以避免碎片引用旧的活动已销毁实例的情况。

当我按下button取消我的地图片段活动上的意图select器时,我得到这个exception。 我解决了这个onResume(在我正在初始化片段)的代码replaceonstart()和应用程序工作正常。希望它有帮助。

我想使用transaction.commitAllowingStateLoss(); 不是最好的解决scheme。 当活动的configuration发生变化并且onSavedInstanceState()被调用,然后你的asynchronouscallback方法尝试提交fragment时,这个exception将被抛出。

简单的解决scheme可以检查活动是否改变configuration

例如check isChangingConfigurations()

if(!isChangingConfigurations()) { //commit transaction. }

检出这个链接

每当你试图在你的活动中加载一个片段时,确保活动处于恢复状态,而不是暂停状态。在暂停状态下,你最终可能会失去提交操作。

您可以使用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 your fragment } 

这是2017年10月,谷歌使Android支持库与新的东西调用生命周期组件。 它为这个'onSaveInstanceState'问题后不能执行这个动作提供了一些新的想法。

简而言之:

  • 使用生命周期组件来确定是否正确popup分段的时间。

更长的版本与解释:

  • 为什么这个问题出来了?

    这是因为你正在尝试从你的活动(这将持有你的片段,我想?)使用FragmentManager来为你提交一个事务片段。 通常这会看起来像你正在尝试做一些交易一个未来的片段,同时主机活动已经调用savedInstanceState方法(用户可能碰巧主页button,所以活动调用onStop() ,在我的情况下,这是原因)

    通常这个问题不应该发生 – 我们总是试图在开始时加载片段,就像onCreate()方法是一个完美的地方。 但是有时候会发生这种情况 ,特别是当你不能确定你要加载到哪个片段,或者你试图从AsyncTask块加载片段(或者需要一些时间)时。 在片段事务真正发生之前,但在activity的onCreate()方法之后,用户可以做任何事情。 如果用户按下主页button,触发活动的onSavedInstanceState()方法,将会有一个can not perform this action崩溃。

    如果有人想在这个问题上看得更深,我build议他们看看这个博客文章 。 它看起来很深入的源代码层,并解释了很多。 此外,它给出的理由,你不应该使用commitAllowingStateLoss()方法来解决这个崩溃(相信我它没有提供任何好处的代码)

  • 如何解决这个问题?

    • 我应该使用commitAllowingStateLoss()方法来加载片段吗? 不,你不应该 ;

    • 我应该重写onSaveInstanceState方法,忽略里面的super方法吗? 不,你不应该 ;

    • 我是否应该使用神奇的isFinishing内部活动来检查主机活动是否适合碎片交易? 是的,这看起来是正确的做法。

  • 看看生命周期组件可以做什么。

    基本上,Google在AppCompatActivity类(以及您应该在项目中使用的其他几个基类)内部实现了一些实现,这使得更容易确定当前的生命周期状态 。 回顾一下我们的问题:为什么会出现这个问题? 这是因为我们在错误的时间做事。 所以我们尽量不要这样做,这个问题将会消失。

    我为自己的项目编写了一些代码,这就是我使用LifeCycle所做的。 我在Kotlin编码。

 val hostActivity: AppCompatActivity? = null // the activity to host fragments. It's value should be properly initialized. fun dispatchFragment(frag: Fragment) { hostActivity?.let { if(it.lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)){ showFragment(frag) } } } private fun showFragment(frag: Fragment) { hostActivity?.let { Transaction.begin(it, R.id.frag_container) .show(frag) .commit() } 

如上所示。 我将检查主机活动的生命周期状态。 使用支持库中的生命周期组件,这可能更具体。 代码lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)意思是,如果当前状态至less是onResume ,不迟于它? 这确保我的方法不会在其他生命状态(如onStop )中执行。

  • 这一切都完成了吗?

    当然不是。 我所显示的代码告诉了一些新的方法来防止应用程序崩溃。 但是,如果它进入onStop状态,那么这行代码将不会执行任何操作,从而在屏幕上不显示任何内容。 当用户回到应用程序,他们将看到一个空的屏幕,这是空的主机活动,根本没有显示片段。 这是不好的经验(是的,比碰撞好一点点)。

    所以在这里,我希望可以有更好的东西:应用程序不会崩溃,如果它的生命状态晚于onResume ,事务方法是生命状态感知; 此外,活动将尝试继续完成该片段交易行为,用户回到我们的应用程序之后。

    我为这个方法增加了更多的东西:

 class FragmentDispatcher(_host: FragmentActivity) : LifecycleObserver { private val hostActivity: FragmentActivity? = _host private val lifeCycle: Lifecycle? = _host.lifecycle private val profilePendingList = mutableListOf<BaseFragment>() @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun resume() { if (profilePendingList.isNotEmpty()) { showFragment(profilePendingList.last()) } } fun dispatcherFragment(frag: BaseFragment) { if (lifeCycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) { showFragment(frag) } else { profilePendingList.clear() profilePendingList.add(frag) } } private fun showFragment(frag: BaseFragment) { hostActivity?.let { Transaction.begin(it, R.id.frag_container) .show(frag) .commit() } } } 

我在这个dispatcher类中维护一个列表,存储这些片段没有机会完成事务操作。 而当用户从主屏幕回来,发现还有片段正在等待启动时​​,它将转到@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)注释下的resume()方法。 现在我认为它应该像我预期的那样工作。

在你的活动中添加这个

 @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (outState.isEmpty()) { // Work-around for a pre-Android 4.2 bug outState.putBoolean("bug:fix", true); } } 

从支持库版本24.0.0开始,您可以调用同步提交此事务的FragmentTransaction.commitNow()方法,而不是先调用commit()后再执行executePendingTransactions() 。 正如文件所说,这种方法甚至更好:

调用commitNow比调用commit()后跟executePendingTransactions()更可取,因为后者将具有尝试提交所有当前未决事务的副作用,无论这是否是所需的行为。

我也遇到过这个问题,每当你的FragmentActivity上下文被改变时(例如屏幕方向改变等),就会出现问题。 所以最好的解决办法是从你的FragmentActivity更新上下文。

在我的案例中,可能最顺利也是最简单的解决scheme是避免将违规的碎片从作业结果中popup堆栈。 所以改变我的onActivityResult()这个调用:

 popMyFragmentAndMoveOn(); 

对此:

 new Handler(Looper.getMainLooper()).post(new Runnable() { public void run() { popMyFragmentAndMoveOn(); } } 

在我的情况帮助。

这里抛出exception(在FragmentActivity中):

 @Override public void onBackPressed() { if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) { super.onBackPressed(); } } 

FragmentManager.popBackStatckImmediate() ,首先调用FragmentManager.checkStateLoss() 。 这是IllegalStateException的原因。 看下面的实现:

 private void checkStateLoss() { if (mStateSaved) { // Boom! throw new IllegalStateException( "Can not perform this action after onSaveInstanceState"); } if (mNoTransactionsBecause != null) { throw new IllegalStateException( "Can not perform this action inside of " + mNoTransactionsBecause); } } 

我简单地通过使用标志来标记Activity的当前状态来解决这个问题。 这是我的解决scheme:

 public class MainActivity extends AppCompatActivity { /** * A flag that marks whether current Activity has saved its instance state */ private boolean mHasSaveInstanceState; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onSaveInstanceState(Bundle outState) { mHasSaveInstanceState = true; super.onSaveInstanceState(outState); } @Override protected void onResume() { super.onResume(); mHasSaveInstanceState = false; } @Override public void onBackPressed() { if (!mHasSaveInstanceState) { // avoid FragmentManager.checkStateLoss()'s throwing IllegalStateException super.onBackPressed(); } } 

}

简单而紧凑的解决scheme可能是:

 @Override public void show(FragmentManager manager, String tag){ FragmentTransaction ft=manager.beginTransaction(); ft.add(this,tag); ft.commitAllowingStateLoss(); }