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

IllegalStateException: Can not perform this action after onSaveInstanceState with ViewPager

提问人:nhaarman 提问时间:9/28/2011 最后编辑:Mollynhaarman 更新时间:9/25/2023 访问量:415691

问:

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

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

郑重声明:我有一个选项卡主机,在每个选项卡中都有一个在活动之间切换的 ActivityGroup。

android-fragments android-viewpager illegalstateexception fragmenttransaction

评论

2赞 nhaarman 9/28/2011
我发现这个问题讨论了同一个问题,但也没有解决方案。stackoverflow.com/questions/7469082/......
3赞 CommonsWare 9/28/2011
虽然您不使用 ,但 Honeycomb 肯定是。这真的发生在真正的蜂窝片上吗?或者可能是有人在手机或其他东西上运行一个被黑的 Honeycomb,而那个被黑的版本遇到了困难?FragmentManager
1赞 nhaarman 9/28/2011
我不知道。这是我在市场开发者控制台中获得的唯一信息,用户消息也不包含任何有用的信息。
0赞 nhaarman 9/28/2011
我正在使用 Flurry,它向我展示了 Android 3.0.1 的 11 个会话,我有 11 个关于此异常的报告。不过可能是巧合。Android 3.1 和 3.2 分别有 56 个和 38 个会话。
0赞 Nikolay Elenkov 9/28/2011
市场错误报告有一个“平台”部分,有时它包含设备的 Android 版本。

答:

10赞 saberrider 2/21/2012 #1

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

public void onBackPressed() {}

并在其中编写一些代码。即使旧设备上没有这样的方法,新设备也会调用此方法。Activityback

774赞 Ovidiu Latcu 4/22/2012 #2

在这里查看我的答案。基本上我只需要:

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

不要对方法进行调用。这把事情搞砸了......super()saveInstanceState

这是支持包中的已知错误

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

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

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

transaction.commitAllowingStateLoss();

添加或执行导致 .FragmentTransactionException

评论

406赞 meh 9/28/2012
您应该使用 commitAllowingStateLoss() 而不是 commit()
21赞 Risadinha 10/16/2013
这个关于 commitAllowingStateLoss() 的评论本身就是一个答案 - 你应该这样发布它。
20赞 Codeversed 12/17/2013
关于 'commitAllowingStateLoss' --/> “这很危险,因为如果以后需要将活动从其状态恢复,提交可能会丢失,因此这应该只用于 UI 状态在用户身上意外更改的情况。”
12赞 Synesso 1/9/2015
如果我查看 v4 源代码,如果状态已保存,它会立即失败。以前添加片段不会起到任何作用。我的测试表明这是真的。它对此特定异常没有影响。我们需要的是一种方法。popBackStackImmediatecommitAllowingStateLosspopBackStackImmediateAllowingStateLoss
3赞 Synesso 3/9/2015
@DanieleB是的,我在这里发布了一个答案。但实际上,通过使用 Otto 消息总线,我找到了一个更好的解决方案:将片段注册为订阅者并侦听来自总线的异步结果。暂停时取消注册,恢复时重新注册。异步还需要一个 Produce 方法,用于完成和片段暂停的时间。当我有时间时,我会更详细地更新我的答案。
22赞 Jed 10/16/2012 #3

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

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

评论

7赞 pjv 11/26/2012
根据您正在执行的不允许的操作,这可能需要移动到比 onResume() 更晚的时刻。对于 insatcne,如果 FragmentTransaction.commit() 是问题所在,则需要将其改为 onPostResume()。
1赞 Janis Peisenieks 7/16/2013
对我来说,这就是这个问题的答案。由于我需要将收到的 NFC 标签转发到上一个活动,因此这就是我所做的。
7赞 Sufian 2/10/2015
对我来说,它之所以发生,是因为我没有打电话。super.onActivityResult()
21赞 Vinayak 4/8/2014 #4

简短而有效的解决方案:

遵循简单的步骤

步骤

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

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Step 2 : 使用fragmentTransaction.commitAllowingStateLoss( );

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

评论

0赞 Vinayak 10/27/2017
答案不是复制或引用的,否则。是发布来帮助人们通过我的工作解决方案,这是通过多次试验和错误获得的
3赞 DeepCodes 7/14/2014 #5

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

148赞 Synesso 1/9/2015 #6

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

如果会话状态已保存,则此方法调用(如 )将始终失败。检查来源。您无法执行任何操作来阻止引发此异常。popBackStackIllegalStateException

  • 删除对 的调用将无济于事。super.onSaveInstanceState
  • 创建 Fragment 将无济于事。commitAllowingStateLoss

以下是我观察问题的方式:

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

以下是我为解决它所做的工作:

由于无法避免回调中的 in,因此 catch 并忽略它。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;
    }
}

评论

6赞 Pascal 4/18/2016
有趣的阅读在这里:androiddesignpatterns.com/2013/08/......
0赞 android developer 10/22/2016
如果您使用 DialogFragment,我在这里做了一个替代方案:github.com/AndroidDeveloperLB/DialogShard
0赞 Kimi Chiu 11/25/2016
如果 Android 本身调用呢?popBackStackImmediate
2赞 Neonigma 5/15/2018
绝对很棒。这应该是公认的答案。谢谢!也许我会添加submitPressed = false;在 popBackStackInmediate 之后。
0赞 Fakhriddin Abdullaev 1/23/2020
我没有使用public void onSaveInstanceState(Bundle outState)方法。我需要为 public void onSaveInstanceState(Bundle outState) 设置 empty 方法吗?
63赞 Naskov 3/4/2015 #7

检查活动是否在显示片段之前,并注意。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();
}

评论

2赞 Allen Vork 2/6/2018
!isFinishing() && !isDestroyed() 对我不起作用。
0赞 CoolMind 2/1/2019
!isFinishing() & !isDestroyed() 对我有用,但它需要 API 17。但它根本没有显示.请参阅 stackoverflow.com/questions/15729138/...,了解其他好的解决方案,stackoverflow.com/a/41813953/2914140 帮助了我。DialogFragment
5赞 Mina Wissa 7/18/2015 #8

我有一个类似的问题,场景是这样的:

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

活动的 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();
        }

引发异常的原因是,当配置更改(设备轮换)时,会创建活动,从片段管理器的历史记录中检索主片段,同时该片段已经具有对已销毁活动的旧引用

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

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

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

2赞 Amol Desai 8/13/2015 #9

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

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

例如,检查isChangingConfigurations()

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

也请查看链接

评论

0赞 android developer 9/21/2015
不知何故,当用户单击某些内容时,我遇到了此异常(单击是执行事务提交的触发器)。这怎么可能?你的解决方案会在这里吗?
0赞 Amol Desai 9/23/2015
@androiddeveloper您在用户点击时还做了什么。不知何故,Fragment 在提交事务之前保存其状态
0赞 android developer 9/24/2015
异常是在事务提交的确切行上引发的。另外,我有一个奇怪的错别字:我的意思不是“在这里”,而是“在这里工作”。
0赞 Amol Desai 9/24/2015
@androiddeveloper你是对的!但是在提交事务之前,您是否生成了任何后台线程或其他东西?
0赞 android developer 9/24/2015
我不这么认为(对不起,我不在办公室),但这有什么关系呢?这里都是UI的东西......如果我在后台线程上做一些事情,我会在那里遇到异常,而且我不会把与UI相关的东西放在后台线程上,因为这太冒险了。
14赞 Eric Brandwein 11/13/2015 #10

请注意,使用可能会给用户带来糟糕的体验。有关引发此异常的原因的详细信息,请参阅此帖子transaction.commitAllowingStateLoss()

评论

9赞 Umar Ata 2/13/2018
这不会提供问题的答案,您必须为问题提供有效的答案
0赞 yifan 3/3/2016 #11

将此添加到您的活动中

@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);
    }
}
0赞 Adam 4/1/2016 #12

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

1赞 Volodymyr 8/30/2016 #13

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

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

6赞 Chandler 12/20/2016 #14

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

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

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

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
4赞 mojuba 6/3/2017 #15

在我的案例中,我发现的最顺利和最简单的解决方案可能是避免根据活动结果将有问题的片段从堆栈中弹出。所以在我的 :onActivityResult()

popMyFragmentAndMoveOn();

对此:

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

对我有帮助。

2赞 Frost Lau 8/7/2017 #16

此处抛出异常(在 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();
        }
    }
}
2赞 Sharath kumar 9/13/2017 #17

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

您可以使用 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
}
39赞 Anthonyeef 10/4/2017 #18

现在是 2017 年 10 月,Google 制作了 Android 支持库,其中包含称为 Lifecycle 组件的新事物。它为这个“在 onSaveInstanceState 之后无法执行此操作”问题提供了一些新的想法。

总之:

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

加长版说明:

  • 为什么会出现这个问题?

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

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

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

  • 如何解决这个问题?

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

    • 我应该覆盖方法,忽略其中的方法吗?不,你不应该onSaveInstanceStatesuper;

    • 我是否应该使用神奇的内部活动来检查主机活动是否处于片段交易的正确时刻?是的,这看起来是正确的方法。isFinishing

  • 看看 Lifecycle 组件可以做什么。

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

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

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

  • 都完成了吗?

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

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

评论

9赞 Shchvova 4/28/2018
使用 Java 而不是 Kotlin 会很好
2赞 fraherm 12/11/2018
如果只还原一个片段,为什么使用列表来存储待处理的片段?FragmentDispatcher
0赞 Shareem Gelito Teofilo 8/23/2023
我更喜欢这个答案。谢谢!如果使用 FragmentTransactions,我们至少应该等待处于 START 状态,以防止崩溃。developer.android.com/topic/libraries/architecture/...... developer.android.com/topic/libraries/architecture/......
0赞 Ahmad Melegy 12/16/2017 #19

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

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

然后,当我尝试显示一个片段时,我使用而不是showAllowingStateLossshow

喜欢这个:

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

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

0赞 android developer 2/9/2018 #20

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

@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();
        }
    }
}
2赞 anoop ghildiyal 4/11/2018 #21

如果你在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;
    }
}

评论

0赞 Micer 4/11/2018
请不要将代码作为图像发布,而是使用代码格式。
1赞 sandhya sasane 9/16/2018
是的,它帮助了我..,这是棉花糖设备的确切和适当的解决方案,我得到了这个异常并用这个简单的技巧解决了..!!因此投票赞成。
1赞 ralphgabb 5/18/2018 #22

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

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

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

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

这篇文章对 commitAllowingStateLoss() 很有帮助,并且再也没有出现过片段问题。

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

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

0赞 Samet ÖZTOPRAK 6/13/2018 #23

我有完全相同的问题。 它的发生是因为先前活动的破坏。 当 ı 支持以前的活动时,它被摧毁了。 我把它放在基本活动(错误)

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    SpinnerCustom2.setFragmentManager(getSupportFragmentManager());
    onCreateDrawerActivity(savedInstanceState);
}

我把它放在onStart中,它是对的

@Override
protected void onStart() {
    super.onStart();
    SpinnerCustom2.setFragmentManager(getSupportFragmentManager());

}
1赞 Levon Petrosyan 6/22/2018 #24

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

评论

0赞 Hamid Zandi 12/24/2019
它不够好,而且有问题(深度链接处理不佳,无法保存状态显示/隐藏片段以及一些仍然悬而未决的重要问题)
3赞 Irshad Kumail 7/1/2018 #25

友情提供:IllegalStateException 的解决方案

这个问题困扰了我很长时间,但幸运的是,我提出了一个具体的解决方案。它的详细说明在这里。

使用 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。这个想法是只有当活动在前台时才将 trasnsaction 标记为安全,这样就不会有状态丢失的机会。

/*
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;
 }
}
2赞 MorZa 7/17/2018 #26

关于@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();
    }
}
5赞 Lawrence Kesteloot 12/1/2018 #27

如果继承自 ,则必须调用 :FragmentActivityonActivityResult()

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

如果不这样做并尝试在该方法中显示片段对话框,则可能会得到 OP 的 .(老实说,我不太明白为什么超级调用可以解决问题。 之前调用,因此仍不应允许显示片段对话框。IllegalStateExceptiononActivityResult()onResume()

评论

1赞 Big McLargeHuge 10/17/2019
很想知道为什么这可以解决问题。
2赞 Tapa Save 12/12/2018 #28

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

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

这也对我有用。

评论

0赞 Itay Feldman 12/31/2019
请注意,它需要 API 26 及更高版本
2赞 Hanako 1/25/2019 #29

就我而言,我在名为 onActivityResult 的覆盖方法中遇到了此错误。挖掘之后,我才发现也许我之前需要称呼“超级”。
我添加了它,它就工作了

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

}

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

0赞 no_cola 2/13/2019 #30

@Gian Gomen:就我而言,打电话给SUPER可以解决问题。似乎比commitAllowingStateLoss()更正确的解决方案,因为它解决了问题,而不是隐藏它。

@Override
public void onRequestPermissionsResult(
     final int requestCode,
     @NonNull final String[] permissions, 
     @NonNull final int[] grantResults
) {
        super.onRequestPermissionsResult(requestCode,permissions, grantResults); //<--- Without this line crash 
        switch (requestCode) {
            case Constants.REQUEST_CODE_PERMISSION_STORAGE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    onPermissionGranted(Constants.REQUEST_CODE_PERMISSION_STORAGE);
                }
                break;
        }

0赞 kim 3/7/2019 #31

如果状态已保存,请使用 remove() 而不是 popup()。

   private void removeFragment() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    if (fragmentManager.isStateSaved()) {
        List<Fragment> fragments = fragmentManager.getFragments();
        if (fragments != null && !fragments.isEmpty()) {
            fragmentManager.beginTransaction().remove(fragments.get(fragments.size() - 1)).commitAllowingStateLoss();
        }
    }
}
5赞 Ivo Stoyanov 6/21/2019 #32

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

2赞 CoolMind 7/23/2019 #33

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://stackoverflow.com/a/59158254/2914140
        addToBackStack(tag)
    }?.commitAllowingStateLoss()
}

用法:

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

评论

0赞 Claire 3/5/2020
您可以删除 Fragment 之前的 () ->
0赞 CoolMind 3/5/2020
@Claire,谢谢!你的意思是改成吗?是的,我尝试了这个变体,但在这种情况下,在所有情况下都会创建一个片段(即使 fragmentManager == null,但我没有遇到这种情况)。我更新了答案并将 null 更改为 标记。fragment: FragmentaddToBackStack()
2赞 user17576552 12/3/2021 #34

更改为 .不要使用 parent,尽量使用 self。getFragmentManager()getChildFragmentManager()FragmentManager

2赞 Njuacha Hubert 9/19/2022 #35

正如您在崩溃报告中看到的,引发异常的最后一行是

checkStateLoss(FragmentManager.java:1109)

如果你看一下checkStateLoss的实现

private void checkStateLoss() {
    if (isStateSaved()) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
}

因此,对我来说,一个简单的解决方案是找到您在应用程序中调用的 Fragment Manager 的任何方法,最终导致该方法被调用,并在调用该方法之前检查 isStateSaved() 是否为 false。对我来说,这是show()方法。我确实喜欢这个

if (!isStateSaved()) {
  myDialog.show(fragmentManager, Tag)
}
0赞 Lynda Douglas 9/25/2023 #36

我有同样的问题,但就我而言,我的xml文件中有错误。我在按钮和文本框之间添加了不再允许的空格。当我删除它们时,我没有收到任何有关此错误的报告。enter image description here