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

Posted

技术标签:

【中文标题】IllegalStateException:在使用 ViewPager onSaveInstanceState 后无法执行此操作【英文标题】:IllegalStateException: Can not perform this action after onSaveInstanceState with ViewPager 【发布时间】:2011-11-26 10:00:05 【问题描述】:

我从市场上的应用程序中获取用户报告,出现以下异常:

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 没有显示我自己的任何类,所以我不知道这个异常发生在哪里以及如何防止它。

记录一下:我有一个tabhost,在每个tab中都有一个ActivityGroup在Activity之间切换。

【问题讨论】:

我发现这个问题在讨论同样的问题,但也没有解决方案。***.com/questions/7469082/… 虽然您没有使用FragmentManager,但 Honeycomb 肯定是。这是否发生在真正的 Honeycomb 平板电脑上?或者可能是有人在手机上运行了一个被黑的 Honeycomb 或者其他什么东西,而那个被黑的版本遇到了困难? 我不知道。这是我在市场开发者控制台中获得的唯一信息,用户消息也没有包含任何有用的信息.. 我正在使用 Flurry,它显示了我使用 Android 3.0.1 的 11 次会话,并且我有 11 次关于此异常的报告。不过可能是巧合。 Android 3.1 和 3.2 分别有 56 和 38 个会话。 市场错误报告有一个“平台”部分,有时其中包含设备的 Android 版本。 【参考方案1】:

请查看我的回答here。基本上我只需要:

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

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

这是支持包中已知的bug。

如果您需要保存实例并向您的 outState Bundle 添加一些内容,您可以使用以下内容:

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

最后,正确的解决方案是(如 cmets 所示)使用:

transaction.commitAllowingStateLoss();

当添加或执行导致ExceptionFragmentTransaction 时。

【讨论】:

你应该使用 commitAllowingStateLoss() 而不是 commit() 这条关于 commitAllowingStateLoss() 的评论本身就是一个答案——你应该这样发布。 关于 'commitAllowingStateLoss' --/> "这很危险,因为如果活动需要稍后从其状态恢复,提交可能会丢失,所以这应该只用于可以的情况用户界面状态会意外更改。” 如果我查看 popBackStackImmediate 的 v4 源代码,如果状态已保存,它会立即失败。以前使用commitAllowingStateLoss 添加片段没有任何作用。我的测试表明这是真的。它对此特定异常没有影响。我们需要的是一个popBackStackImmediateAllowingStateLoss 方法。 @DanieleB 是的,我已经在这里发布了答案。但实际上我通过使用 Otto 消息总线找到了一个更好的解决方案:将片段注册为订阅者并监听来自总线的异步结果。暂停时取消注册并在恢复时重新注册。异步还需要一个 Produce 方法来处理它完成和片段暂停的时间。当我有时间时,我会更详细地更新我的答案。【参考方案2】:

类似的错误信息有很多相关的问题。检查此特定堆栈跟踪的第二行。此异常与对FragmentManagerImpl.popBackStackImmediate 的调用特别相关。

如果会话状态已保存,则此方法调用(如 popBackStack总是失败并返回 IllegalStateException。检查来源。您无法阻止抛出此异常。

删除对super.onSaveInstanceState 的调用将无济于事。 使用commitAllowingStateLoss 创建片段将无济于事。

这是我观察到问题的方式:

有一个带有提交按钮的表单。 单击按钮时会创建一个对话框并启动异步进程。 用户在该过程完成之前单击主页键 - 调用onSaveInstanceState。 流程完成,进行回调并尝试popBackStackImmediateIllegalStateException 被抛出。

这是我为解决这个问题所做的:

由于无法避免回调中的IllegalStateException,因此捕获并忽略它。

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

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

要解决此问题,请在创建对话框时设置一些状态以指示进程已启动。

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);
        submitPressed = false;
    

【讨论】:

有趣的阅读在这里:androiddesignpatterns.com/2013/08/… 如果你使用 DialogFragment,我在这里做了一个替代方案:github.com/AndroidDeveloperLB/DialogShard 如果popBackStackImmediate被Android自己调用了怎么办? 非常棒。这应该是公认的答案。非常感谢!也许我会添加 submitPressed = false;在 popBackStackInmediate 之后。 我没有使用 public void onSaveInstanceState(Bundle outState) 方法。我需要为 public void onSaveInstanceState(Bundle outState) 设置空方法吗?【参考方案3】:

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

例子:

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

【讨论】:

!isFinishing() && !isDestroyed() 对我不起作用。 !isFinishing() && !isDestroyed() 对我有用,但它需要 API 17。但它根本不显示DialogFragment。其他好的解决方案请参见***.com/questions/15729138/…,***.com/a/41813953/2914140 帮助了我。【参考方案4】:

现在是 2017 年 10 月,Google 使用名为 Lifecycle 组件的新事物制作了 Android 支持库。它为这个'Can not perform this action after onSaveInstanceState'问题提供了一些新思路。

简而言之:

使用生命周期组件来确定是否是弹出片段的正确时间。

带解释的加长版:

为什么会出现这个问题?

这是因为您尝试使用活动中的FragmentManager(我想这将保存您的片段?)为您的片段提交事务。通常这看起来就像您正在尝试为即将到来的片段做一些事务,同时主机活动已经调用 savedInstanceState 方法(用户可能碰巧触摸主页按钮,因此活动调用 onStop(),在我的情况下是原因)

通常这个问题不应该发生——我们总是尝试在一开始就将片段加载到活动中,就像onCreate() 方法是一个完美的地方。但有时这种情况确实会发生,尤其是当您无法决定将哪个片段加载到该活动时,或者您试图从 AsyncTask 块加载片段时(或者任何事情都需要一点时间)。在片段事务真正发生之前,但在活动的onCreate() 方法之后,用户可以做任何事情的时间。如果用户按下 Home 按钮,触发 Activity 的 onSavedInstanceState() 方法,则会出现 can not perform this action 崩溃。

如果有人想深入了解这个问题,我建议他们看看这个博客post。它深入到源代码层并解释了很多。此外,它还给出了您不应该使用 commitAllowingStateLoss() 方法来解决此崩溃的原因(相信我,它对您的代码没有任何好处)

如何解决这个问题?

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

我应该覆盖onSaveInstanceState 方法,忽略其中的super 方法吗? 不应该

我是否应该使用神奇的isFinishing 内部活动来检查主机活动是否在正确的时刻进行片段事务?是的,这看起来是正确的做法。

看看Lifecycle组件能做什么。

基本上,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) 的意思是,如果当前状态至少是onResume,不迟于它?这确保我的方法不会在其他生命状态下执行(如onStop)。

一切都完成了吗?

当然不是。我展示的代码告诉了一些防止应用程序崩溃的新方法。但如果它确实进入onStop 的状态,那行代码将不会做任何事情,因此不会在您的屏幕上显示任何内容。当用户返回应用程序时,他们将看到一个空屏幕,即空的主机活动,根本没有显示任何片段。这是糟糕的体验(是的,比崩溃好一点)。

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

我在这个方法中添加了更多内容:

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()方法。现在我认为它应该像我预期的那样工作。

【讨论】:

用 Java 代替 Kotlin 会更好 如果只恢复一个片段,为什么您的FragmentDispatcher 实现使用列表来存储待处理的片段?【参考方案5】:

这是解决此问题的不同方法。

使用私有成员变量,您可以将返回的数据设置为可以在 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;
    

【讨论】:

根据您所做的不允许的操作,这可能需要移到比 onResume() 更晚的时刻。对于 insatcne,如果 FragmentTransaction.commit() 是问题,这需要进入 onPostResume() 代替。 这对我来说就是这个问题的答案。由于我需要将收到的 NFC 标签转发到上一个活动,这就是为我做的。 对我来说这是因为我没有打电话给super.onActivityResult()【参考方案6】:

短而有效的解决方案:

遵循简单的步骤

步骤

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

 @Override
public void onSaveInstanceState( Bundle outState ) 

  

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

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

【讨论】:

答案不会被复制或参考其他形式。我通过我的工作解决方案发布以帮助人们,该解决方案是通过多次试验和错误获得的【参考方案7】:

注意,使用transaction.commitAllowingStateLoss() 可能会导致用户体验不佳。有关引发此异常的原因的更多信息,请参阅this post。

【讨论】:

这不提供问题的答案,您必须提供问题的有效答案【参考方案8】:

我为这类问题找到了一个肮脏的解决方案。如果您出于任何原因(我有时间限制的原因)仍想保留您的ActivityGroups,您只需实施

public void onBackPressed() 

在您的Activity 中并在其中执行一些back 代码。即使旧设备上没有这样的方法,这个方法也会被新设备调用。

【讨论】:

【参考方案9】:

不要使用 commitAllowingStateLoss(),它应该只用于 UI 状态可以在用户上意外更改的情况。

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

如果事务发生在 parentFragment 的 ChildFragmentManager 中,请使用 parentFragment.isResume() 在外面检查。

if (parentFragment.isResume()) 
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);

【讨论】:

【参考方案10】:

我也遇到过类似的问题,场景是这样的:

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

activityonCreate方法是这样的:

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();
        

抛出异常是因为当配置更改(设备旋转)时,创建了活动,从片段管理器的历史记录中检索主片段,同时片段已经有一个 OLD被破坏的活动的引用

将实现更改为此解决了问题:

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);

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

【讨论】:

【参考方案11】:

如果继承自FragmentActivity,则必须调用onActivityResult()中的超类:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) 
    super.onActivityResult(requestCode, resultCode, intent);
    ...

如果您不这样做并尝试在该方法中显示片段对话框,您可能会得到 OP 的IllegalStateException。 (说实话,我不太明白为什么 super 调用解决了这个问题。onActivityResult()onResume() 之前被调用,所以它仍然不应该被允许显示片段对话框。 )

【讨论】:

很想知道为什么这可以解决问题。【参考方案12】:

在我的案例中,我发现的最流畅和最简单的解决方案可能是避免将有问题的片段从堆栈中弹出以响应活动结果。所以在我的onActivityResult() 中更改此调用:

popMyFragmentAndMoveOn();

到这里:

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

对我有帮助。

【讨论】:

【参考方案13】:

当我按下返回按钮取消地图片段活动上的意图选择器时,我遇到了这个异常。 我通过将 onResume(我正在初始化片段的地方)的代码替换为 onstart() 解决了这个问题,并且应用程序运行良好。希望它有所帮助。

【讨论】:

【参考方案14】:

礼貌:Solution for IllegalStateException

这个问题困扰了我很长时间,但幸运的是我为它提供了一个具体的解决方案。详细解释是here。

使用 commitAllowStateloss() 可能会阻止此异常,但会导致 UI 异常。到目前为止,我们已经了解到,当我们在 Activity 状态丢失后尝试提交片段时会遇到 IllegalStateException - 所以我们应该延迟事务直到状态已恢复。可以这样简单地完成

声明两个私有布尔变量

 public class MainActivity extends AppCompatActivity 

    //Boolean variable to mark if the transaction is safe
    private boolean isTransactionSafe;

    //Boolean variable to mark if there is any transaction pending
    private boolean isTransactionPending;

现在在 onPostResume() 和 onPause 中,我们设置和取消设置布尔变量 isTransactionSafe。想法是仅在活动处于前台时才将交易标记为安全,这样就不会丢失状态。

/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
 */
public void onPostResume()
    super.onPostResume();
    isTransactionSafe=true;

/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
 */

public void onPause()
    super.onPause();
    isTransactionSafe=false;



private void commitFragment()
    if(isTransactionSafe) 
        MyFragment myFragment = new MyFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.frame, myFragment);
        fragmentTransaction.commit();
    

-到目前为止,我们所做的将从 IllegalStateException 中保存,但如果在活动移至后台后完成,我们的事务将丢失,有点像 commitAllowStateloss()。为了帮助解决这个问题,我们有 isTransactionPending 布尔变量

public void onPostResume()
   super.onPostResume();
   isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
   if (isTransactionPending) 
      commitFragment();
   



private void commitFragment()

 if(isTransactionSafe) 
     MyFragment myFragment = new MyFragment();
     FragmentManager fragmentManager = getFragmentManager();
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
     fragmentTransaction.add(R.id.frame, myFragment);
     fragmentTransaction.commit();
     isTransactionPending=false;
 else 
     /*
     If any transaction is not done because the activity is in background. We set the
     isTransactionPending variable to true so that we can pick this up when we come back to
foreground
     */
     isTransactionPending=true;
 

【讨论】:

【参考方案15】:

片段事务不应在Activity.onStop() 之后执行! 检查您是否没有任何可以在onStop() 之后执行事务的回调。最好解决原因,而不是尝试使用 .commitAllowingStateLoss() 之类的方法解决问题

【讨论】:

【参考方案16】:

我认为使用transaction.commitAllowingStateLoss(); 不是最佳解决方案。 当活动的配置更改并调用片段onSavedInstanceState() 并且此后您的异步回调方法尝试提交片段时,将引发此异常。

简单的解决方案可以是检查活动是否正在更改配置

例如检查isChangingConfigurations()

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

结帐this 链接以及

【讨论】:

当用户点击某物时我不知何故得到了这个异常(点击是执行事务提交的触发器)。这怎么可能?你的解决方案在这里吗? @androiddeveloper 你在用户点击时还在做什么。在提交事务之前,fragment 会以某种方式保存其状态 在事务提交的确切行上引发了异常。另外,我有一个奇怪的错字:我的意思不是“在这里”,而是“在这里工作”。 @androiddeveloper 你是对的!但是在提交事务之前,您是否会产生任何后台线程或其他东西? 我不这么认为(对不起,我不在办公室),但这有什么关系呢?这里都是 UI 的东西……如果我在后台线程上做点什么,我会有例外,而且我不会把 UI 相关的东西放在后台线程上,因为这太冒险了。【参考方案17】:

当您尝试在 Activity 中加载片段时,请确保该 Activity 处于恢复状态而不是暂停状态。在暂停状态下,您最终可能会丢失已完成的提交操作。

您可以使用 transaction.commitAllowingStateLoss() 代替 transaction.commit() 来加载片段

创建一个布尔值并检查活动是否不会暂停

@Override
public void onResume() 
    super.onResume();
    mIsResumed = true;


@Override
public void onPause() 
    mIsResumed = false;
    super.onPause();

然后在加载片段时检查

if(mIsResumed)
//load the your fragment

【讨论】:

【参考方案18】:

如果您在 onActivityResult 中执行一些 FragmentTransaction,您可以在 onActivityResult 中设置一些布尔值,然后在 onResume 中,您可以根据布尔值执行 FragmentTransaction。请参考下面的代码。

@Override
protected void onResume() 
    super.onResume;
    if(isSwitchFragment)
        isSwitchFragment=false;
        bottomNavigationView.getTabAt(POS_FEED).select();
    


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) 
    if (requestCode == FilterActivity.FILTER_REQUEST_EVENT && data != null) 
        isSwitchFragment=true;
    

【讨论】:

请不要将代码发布为图像,而是使用代码格式。 是的,它帮助了我..,这是棉花糖设备的准确和正确的解决方案,我得到了这个异常并用这个简单的技巧解决了..!!因此投票赞成。【参考方案19】:

关于@Anthonyeef 的好答案,这里有一个 Java 示例代码:

private boolean shouldShowFragmentInOnResume;

private void someMethodThatShowsTheFragment() 

    if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) 
        showFragment();
     else 
        shouldShowFragmentInOnResume = true;
    


private void showFragment() 
    //Your code here


@Override
protected void onResume() 
    super.onResume();

    if (shouldShowFragmentInOnResume) 
        shouldShowFragmentInOnResume = false;
        showFragment();
    

【讨论】:

【参考方案20】:

这里抛出异常(In 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 的当前状态来解决这个问题。这是我的解决方案:

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();
        
    

【讨论】:

【参考方案21】:

如果您使用 popBackStack() 或 popBackStackImmediate() 方法崩溃,请尝试使用以下方法进行修复:

        if (!fragmentManager.isStateSaved()) 
            fragmentManager.popBackStackImmediate();
        

这对我也有用。

【讨论】:

注意它需要API 26及以上【参考方案22】:

在我的例子中,我在一个名为 onActivityResult 的覆盖方法中遇到了这个错误。挖掘之后,我发现我之前可能需要调用“super”。 我添加了它,它只是工作

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) 
    super.onActivityResult(requestCode, resultCode, data); //<--- THIS IS THE SUPPER CALL
    if (resultCode == Activity.RESULT_OK && requestCode == 0) 
        mostrarFragment(FiltroFragment.newInstance())
    


也许您只需要在代码之前所做的任何覆盖上添加一个“超级”。

【讨论】:

【参考方案23】:

Kotlin 扩展

fun FragmentManager?.replaceAndAddToBackStack(
    @IdRes containerViewId: Int,
    fragment: () -> Fragment,
    tag: String
) 
    // Find and synchronously remove a fragment with the same tag.
    // The second transaction must start after the first has finished.
    this?.findFragmentByTag(tag)?.let 
        beginTransaction().remove(it).commitNow()
    
    // Add a fragment.
    this?.beginTransaction()?.run 
        replace(containerViewId, fragment, tag)
        // The next line will add the fragment to a back stack.
        // Remove if not needed.
        // You can use null instead of tag, but tag is needed for popBackStack(), 
        // see https://***.com/a/59158254/2914140
        addToBackStack(tag)
    ?.commitAllowingStateLoss()

用法:

val fragment =  SomeFragment.newInstance(data) 
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)

【讨论】:

可以去掉Fragment前的()-> @Claire,谢谢!你的意思是改成fragment: Fragment?是的,我尝试了这个变体,但是在这种情况下,在所有情况下都会创建一个片段(即使 fragmentManager == null,但我没有遇到这种情况)。我更新了答案并将addToBackStack()中的null更改为标签。【参考方案24】:

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

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

【讨论】:

【参考方案25】:

我知道@Ovidiu Latcu 有一个接受的答案,但过了一段时间,错误仍然存​​在。

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

Crashlytics 仍然向我发送这个奇怪的错误消息。

但现在仅在版本 7+ (Nougat) 上发生错误 我的解决方法是在 fragmentTransaction 中使用 commitAllowingStateLoss() 而不是 commit()。

这个post 对 commitAllowingStateLoss() 很有帮助,并且再也没有出现片段问题。

总而言之,这里接受的答案可能适用于牛轧糖 android 版本。

这可能会为某人节省几个小时的搜索时间。 快乐的编码。

【讨论】:

【参考方案26】:

要绕过这个问题,我们可以使用在 Google I/O 2018 中引入的The Navigation Architecture Component。 导航架构组件简化了 Android 应用中导航的实现。

【讨论】:

不够好,而且有问题(深度链接处理不佳,无法保存状态显示/隐藏片段以及一些尚未解决的重要问题)【参考方案27】:

将此添加到您的活动中

@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);
    

【讨论】:

【参考方案28】:

我也遇到过这个问题,每次FragmentActivity 的上下文发生变化时都会出现问题(例如屏幕方向发生变化等)。因此,最好的解决方法是从您的 FragmentActivity 更新上下文。

【讨论】:

【参考方案29】:

我最终创建了一个基本片段并让我的应用程序中的所有片段都扩展它

public class BaseFragment extends Fragment 

    private boolean mStateSaved;

    @CallSuper
    @Override
    public void onSaveInstanceState(Bundle outState) 
        mStateSaved = true;
        super.onSaveInstanceState(outState);
    

    /**
     * Version of @link #show(FragmentManager, String) that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) 
        // API 26 added this convenient method
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 
            if (manager.isStateSaved()) 
                return;
            
        

        if (mStateSaved) 
            return;
        

        show(manager, tag);
    

然后,当我尝试显示片段时,我使用 showAllowingStateLoss 而不是 show

像这样:

MyFragment.newInstance()
.showAllowingStateLoss(getFragmentManager(), MY_FRAGMENT.TAG);

我从这个 PR 提出了这个解决方案:https://github.com/googlesamples/easypermissions/pull/170/files

【讨论】:

【参考方案30】:

另一种可能的解决方法,我不确定是否在所有情况下都有帮助(来源here):

@Override
protected void onSaveInstanceState(Bundle outState) 
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) 
            rootView.cancelPendingInputEvents();
        
    

【讨论】:

以上是关于IllegalStateException:在使用 ViewPager onSaveInstanceState 后无法执行此操作的主要内容,如果未能解决你的问题,请参考以下文章

java.lang.IllegalStateException:找不到@SpringBootConfiguration,您需要在测试中使用@ContextConfiguration

在 Java 中使用 iter.remove() 时出现 IllegalStateException(未知来源)

使用 JPA.withTransaction 的测试 Actor(java.lang.IllegalStateException:EntityManager 在测试时关闭)

java.lang.IllegalStateException:无法在 localhost:49167 连接到 Ryuk

java.lang.IllegalStateException: KoinApplication 尚未启动

java.lang.IllegalStateException:在使用 Spring Boot 实现开放 API 时无法读取类的元数据