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

发布于 2024-12-06 13:55:50 字数 2828 浏览 1 评论 0原文

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

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 在“活动”之间切换。

I'm getting user reports from my app in the market, delivering the following exception:

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)

Apparently it has something to do with a FragmentManager, which I don't use. The stacktrace doesn't show any of my own classes, so I have no idea where this exception occurs and how to prevent it.

For the record: I have a tabhost, and in each tab there is a ActivityGroup switching between Activities.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

讽刺将军 2024-12-13 13:55:51

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)

Kotlin extension

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

Usage:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
安静 2024-12-13 13:55:51

将 getFragmentManager() 更改为 getChildFragmentManager()。不要使用父 FragmentManager,尝试使用 self.

change getFragmentManager() to getChildFragmentManager(). Don't use parent FragmentManager, try to use self.

嘦怹 2024-12-13 13:55:51

当父 Activity 或父 Fragment 处于停止状态时尝试创建或替换 Fragment 时,会发生此错误 IllegalStateException:onSaveInstanceState 后无法执行此操作 >用户界面不存在

问。这种状态怎么会发生呢?。

A. 当您的父 Activity 或父 Fragment 已处于停止状态时。当您的应用程序尝试移动到新片段而用户将您的应用程序放回后台时,可能会发生这种情况。

问。显然它与 FragmentManager 有关,我不使用它。堆栈跟踪不显示我自己的任何类,因此我不知道此异常发生在哪里以及如何防止它

A. 当您附加 ViewPager片段。它在内部使用 FragmentManager 来放置片段。

问。如何处理?

答。其他人已经给出了一些解决方案,但我认为您不应该使用解决方法来避免此问题。这是一个真正的问题,你应该优雅地处理它。

在放置片段或附加视图页面之前,您可以使用以下代码来了解 Activity 是否处于停止状态

if(supportFragmentManager.isStateSaved.not()){
        // safe to attach viewpager or fragments
  }

如果在放置片段或附加视图之前位于父片段内,则使用 childFragmentManager.isStateSaved附加视图页面

This error IllegalStateException: Can not perform this action after onSaveInstanceState occurs when you trying to create or replace a fragment while the parent activity or parent fragment is in a Stop State and UI doesn't exist.

Q. How can this state occur?.

A. When your parent activity or parent fragment is already in a stop state. This can happen when your app is trying to move to a new fragment while the user puts your app back to the background.

Q. Apparently it has something to do with a FragmentManager, which I don't use. The stacktrace doesn't show any of my own classes, so I have no idea where this exception occurs and how to prevent it

A. When you attach a ViewPager with Fragments. It internally uses FragmentManager to place fragments.

Q. How to handle it?

A. There are some solutions that others already give but I think you should not use a workaround to avoid this problem. It is a genuine problem and you should handle it gracefully.

You can use the below code to know whether the activity is in a stop state before placing fragments or attaching a viewpager

if(supportFragmentManager.isStateSaved.not()){
        // safe to attach viewpager or fragments
  }

Use childFragmentManager.isStateSaved if inside the parent fragment before placing fragments or attaching viewpager

烟燃烟灭 2024-12-13 13:55:51

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

调用 commitNow 优于先调用 commit() 再调用executePendingTransactions(),因为后者会产生尝试提交所有当前待处理事务的副作用,无论这是否是所需的行为。

Starting from support library version 24.0.0 you can call FragmentTransaction.commitNow() method which commits this transaction synchronously instead of calling commit() followed by executePendingTransactions(). As documentation says this approach even better:

Calling commitNow is preferable to calling commit() followed by executePendingTransactions() as the latter will have the side effect of attempting to commit all currently pending transactions whether that is the desired behavior or not.

無處可尋 2024-12-13 13:55:51

我知道@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欢呼

I know there is an accepted answer by @Ovidiu Latcu but after some while, error still persist.

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

Crashlytics still sending me this weird error message.

However error now occurring only on version 7+ (Nougat)
My fix was to use commitAllowingStateLoss() instead of commit() at the fragmentTransaction.

This post is helpful for commitAllowingStateLoss() and never had a fragment issue ever again.

To sum it up, the accepted answer here might work on pre Nougat android versions.

This might save someone a few hours of searching.
happy codings. <3 cheers

瞳孔里扚悲伤 2024-12-13 13:55:51

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

To bypass this issue, we can use The Navigation Architecture Component , which was introduced in Google I/O 2018.
The Navigation Architecture Component simplifies the implementation of navigation in an Android app.

童话 2024-12-13 13:55:51

将其添加到您的活动中

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

Add this in your activity

@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);
    }
}
你又不是我 2024-12-13 13:55:50

请在此处查看我的答案。基本上我只需要:

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

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

transaction.commitAllowingStateLoss();

添加或执行导致异常FragmentTransaction时。

Please check my answer here. Basically I just had to :

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

Don't make the call to super() on the saveInstanceState method. This was messing things up...

This is a known bug in the support package.

If you need to save the instance and add something to your outState Bundle you can use the following:

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

In the end the proper solution was (as seen in the comments) to use :

transaction.commitAllowingStateLoss();

when adding or performing the FragmentTransaction that was causing the Exception.

凌乱心跳 2024-12-13 13:55:50

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

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

  • 删除对 super.onSaveInstanceState 的调用不会有帮助。
  • 使用 commitAllowingStateLoss 创建片段不会有帮助。

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

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

以下是我解决此问题的方法:

由于无法避免回调中的 IllegalStateException,因此 catch &忽略它。

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;

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

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

There are many related problems with a similar error message. Check the second line of this particular stack trace. This exception is specifically related to the call to FragmentManagerImpl.popBackStackImmediate.

This method call, like popBackStack, will always fail with IllegalStateException if the session state has already been saved. Check the source. There is nothing you can do to stop this exception being thrown.

  • Removing the call to super.onSaveInstanceState will not help.
  • Creating the Fragment with commitAllowingStateLoss will not help.

Here's how I observed the problem:

  • There's a form with a submit button.
  • When the button is clicked a dialog is created and an async process starts.
  • The user clicks the home key before the process is finished - onSaveInstanceState is called.
  • The process completes, a callback is made and popBackStackImmediate is attempted.
  • IllegalStateException is thrown.

Here's what I did to solve it:

As it is not possible to avoid the IllegalStateException in the callback, catch & ignore it.

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

This is enough to stop the app from crashing. But now the user will restore the app and see that the button they thought they'd pressed hasn't been pressed at all (they think). The form fragment is still showing!

To fix this, when the dialog is created, make some state to indicate the process has started.

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

And save this state in the bundle.

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

Don't forget to load it back again in onViewCreated

Then, when resuming, rollback the fragments if submit was previously attempted. This prevents the user from coming back to what seems like an un-submitted form.

@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;
    }
}
忆离笙 2024-12-13 13:55:50

在显示片段之前检查 Activity 是否 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();
}

Check if the activity isFinishing() before showing the fragment and pay attention to commitAllowingStateLoss().

Example:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}
醉殇 2024-12-13 13:55:50

2017 年 10 月,Google 使用名为 Lifecycle 组件的新东西制作了 Android 支持库。它为“在 onSaveInstanceState 之后无法执行此操作”问题提供了一些新的想法。

简而言之:

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

带有解释的较长版本:

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

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

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

    如果有人想更深入地了解这个问题,我建议他们看看这个博客帖子。它深入了解源代码层并解释了很多相关内容。另外,它还给出了您不应该使用 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之后进入生命状态,则不会崩溃,事务方法是生命状态感知的;此外,当用户返回我们的应用程序后,该活动将尝试继续完成该片段事务操作。

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

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

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

It's October 2017, and Google makes Android Support Library with the new things call Lifecycle component. It provides some new idea for this 'Can not perform this action after onSaveInstanceState' problem.

In short:

  • Use lifecycle component to determine if it's correct time for popping up your fragment.

Longer version with explain:

  • why this problem come out?

    It's because you are trying to use FragmentManager from your activity(which is going to hold your fragment I suppose?) to commit a transaction for you fragment. Usually this would look like you are trying to do some transaction for an up coming fragment, meanwhile the host activity already call savedInstanceState method(user may happen to touch the home button so the activity calls onStop(), in my case it's the reason)

    Usually this problem shouldn't happen -- we always try to load fragment into activity at the very beginning, like the onCreate() method is a perfect place for this. But sometimes this do happen, especially when you can't decide what fragment you will load to that activity, or you are trying to load fragment from an AsyncTask block(or anything will take a little time). The time, before the fragment transaction really happens, but after the activity's onCreate() method, user can do anything. If user press the home button, which triggers the activity's onSavedInstanceState() method, there would be a can not perform this action crash.

    If anyone want to see deeper in this issue, I suggest them to take a look at this blog post. It looks deep inside the source code layer and explain a lot about it. Also, it gives the reason that you shouldn't use the commitAllowingStateLoss() method to workaround this crash(trust me it offers nothing good for your code)

  • How to fix this?

    • Should I use commitAllowingStateLoss() method to load fragment? Nope you shouldn't;

    • Should I override onSaveInstanceState method, ignore super method inside it? Nope you shouldn't;

    • Should I use the magical isFinishing inside activity, to check if the host activity is at the right moment for fragment transaction? Yeah this looks like the right way to do.

  • Take a look at what Lifecycle component can do.

    Basically, Google makes some implementation inside the AppCompatActivity class(and several other base class you should use in your project), which makes it a easier to determine current lifecycle state. Take a look back to our problem: why would this problem happen? It's because we do something at the wrong timing. So we try not to do it, and this problem will be gone.

    I code a little for my own project, here is what I do using LifeCycle. I code in 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()
    }

As I show above. I will check the lifecycle state of the host activity. With Lifecycle component within support library, this could be more specific. The code lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED) means, if current state is at least onResume, not later than it? Which makes sure my method won't be execute during some other life state(like onStop).

  • Is it all done?

    Of course not. The code I have shown tells some new way to prevent application from crashing. But if it do go to the state of onStop, that line of code wont do things and thus show nothing on your screen. When users come back to the application, they will see an empty screen, that's the empty host activity showing no fragments at all. It's bad experience(yeah a little bit better than a crash).

    So here I wish there could be something nicer: app won't crash if it comes to life state later than onResume, the transaction method is life state aware; besides, the activity will try continue to finished that fragment transaction action, after the user come back to our app.

    I add something more to this method:

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

I maintain a list inside this dispatcher class, to store those fragment don't have chance to finish the transaction action. And when user come back from home screen and found there is still fragment waiting to be launched, it will go to the resume() method under the @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) annotation. Now I think it should be working like I expected.

写给空气的情书 2024-12-13 13:55:50

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

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

Here is a different solution to this problem.

Using a private member variable you are able to set the returned data as an intent that can then be processed after super.onResume();

Like so:

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;
    }
}
土豪 2024-12-13 13:55:50

简短且可行的解决方案:

遵循简单的步骤

步骤

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

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

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

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

Short And working Solution :

Follow Simple Steps

Steps

Step 1 : Override onSaveInstanceState state in respective fragment. And remove super method from it.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Step 2 : Use
fragmentTransaction.commitAllowingStateLoss( );

instead of fragmentTransaction.commit( ); while fragment operations.

泪痕残 2024-12-13 13:55:50

注意,使用 transaction.commitAllowingStateLoss() 可能会给用户带来糟糕的体验。有关引发此异常的原因的更多信息,请参阅这篇文章

BEWARE, using transaction.commitAllowingStateLoss() could result in a bad experience for the user. For more information on why this exception is thrown, see this post.

明明#如月 2024-12-13 13:55:50

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

public void onBackPressed() {}

在您的 Activity 中实现并执行一些back代码在那里。即使旧设备上没有此类方法,新设备也会调用此方法。

I found a dirty solution for this kind of problem. If you still want to keep your ActivityGroups for whatever reason (I had time limitation reasons), you just implement

public void onBackPressed() {}

in your Activity and do some back code in there. even if there is no such Method on older Devices, this Method gets called by newer ones.

陪你到最终 2024-12-13 13:55:50

不要使用 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);
}

Do not use commitAllowingStateLoss(), it should only be used for cases where it is okay for the UI state to change unexpectedly on the user.

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

If the transaction happens in ChildFragmentManager of parentFragment, use
parentFragment.isResume() outside to check instead.

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
甜心 2024-12-13 13:55:50

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

  • 我的活动正在添加/替换列表片段。
  • 每个列表片段都有一个对活动的引用,以便在单击列表项时通知活动(观察者模式)。
  • 每个列表片段都会在其 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();
        }

抛出异常是因为当配置改变(设备旋转)时,activity被创建,主要fragment是从片段管理器的历史记录,同时片段已经具有对已销毁活动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);

您需要每次创建活动时设置监听器以避免出现以下情况这些片段引用了该活动的旧已销毁实例。

I had a similar problem, the scenario was like this:

  • My Activity is adding/replacing list fragments.
  • Each list fragment has a reference to the activity, to notify the activity when a list item is clicked (observer pattern).
  • Each list fragment calls setRetainInstance(true); in its onCreate method.

The onCreate method of the activity was like 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)));
mMainFragment.setOnSelectionChangedListener(this);
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }

The exception was thrown because the when configuration changes (device rotated), the activity is created, the main fragment is retrieved from the history of the fragment manager and at the same time the fragment already has an OLD reference to the destroyed activity

changing the implementation to this solved the problem:

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

you need to set your listeners each time the activity is created to avoid the situation where the fragments have references to old destroyed instances of the activity.

单身情人 2024-12-13 13:55:50

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

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

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

If you inherit from FragmentActivity, you must call the superclass in onActivityResult():

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

If you don't do this and try to show a fragment dialog box in that method, you may get OP's IllegalStateException. (To be honest, I don't quite understand why the super call fixes the problem. onActivityResult() is called before onResume(), so it should still not be allowed to show a fragment dialog box.)

淡莣 2024-12-13 13:55:50

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

Fragment transactions should not be executed after Activity.onStop() !
Check that you do not have any callbacks that could execute transaction after onStop(). It is better to fix the reason instead of trying to walk around the problem with approaches like .commitAllowingStateLoss()

遗心遗梦遗幸福 2024-12-13 13:55:50

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

popMyFragmentAndMoveOn();

为:

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

对我的情况有所帮助。

Possibly the smoothest and the simplest solution I found in my case was to avoid popping the offending fragment off the stack in response to activity result. So changing this call in my onActivityResult():

popMyFragmentAndMoveOn();

to this:

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

helped in my case.

红衣飘飘貌似仙 2024-12-13 13:55:50

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

checkStateLoss(FragmentManager.java:1109)

如果您查看 checkStateLoss 的实现,

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

所以对我来说一个简单的解决方案是找到您在应用程序中调用的片段管理器的方法,最终导致该方法被调用,并在调用该方法之前简单地检查 isStateSaved() 是否为 false。对我来说就是 show() 方法。我确实喜欢这个

if (!isStateSaved()) {
  myDialog.show(fragmentManager, Tag)
}

as you can see in your crash report, the last line that is throwing the exception is

checkStateLoss(FragmentManager.java:1109)

if you look at the implementation of checkStateLoss

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

so a simple solution for me is to find what ever method of the Fragment Manager you are calling in your app that eventually leads to this method being called and simply check if isStateSaved() is false before calling that method. For me it was the show() method. I did like this

if (!isStateSaved()) {
  myDialog.show(fragmentManager, Tag)
}
娇俏 2024-12-13 13:55:50

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

I was getting this exception when i was pressing back button to cancel intent chooser on my map fragment activity.
I resolved this by replacing the code of onResume(where i was initializing the fragment) to onstart() and the app is working fine.Hope it helps.

你又不是我 2024-12-13 13:55:50

礼貌: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。想法是仅当活动位于前台时才将交易标记为安全,这样就不会出现状态丢失的情况。

/*
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;
 }
}

Courtesy: Solution for IllegalStateException

This issue had annoyed me for a lot of time but fortunately I came with a concrete solution for it. A detailed explanation of it is here.

Using commitAllowStateloss() might prevent this exception but would lead to UI irregularities.So far we have understood that IllegalStateException is encountered when we try to commit a fragment after the Activity state is lost- so we should just delay the transaction until the state is restored.It can be simply done like this

Declare two private boolean variables

 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;

Now in onPostResume() and onPause we set and unset our boolean variable isTransactionSafe. Idea is to mark trasnsaction safe only when the activity is in foreground so there is no chance of stateloss.

/*
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();
    }
}

-What we have done so far will save from IllegalStateException but our transactions will be lost if they are done after the activity moves to background, kind of like commitAllowStateloss(). To help with that we have isTransactionPending boolean variable

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;
 }
}
初心未许 2024-12-13 13:55:50

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

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

例如检查 isChangingConfigurations()

if(!isChangingConfigurations()) {
//提交事务。
}

签出链接为出色地

I think using transaction.commitAllowingStateLoss(); is not best solution.
This exception will be thrown when activity's configuration changed and fragment onSavedInstanceState() is called and thereafter your async callback method tries to commit fragment.

Simple solution could be check whether activity is changing configuration or not

e.g. check isChangingConfigurations()

i.e.

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

Checkout this link as well

怀念你的温柔 2024-12-13 13:55:50

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

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

Whenever you are trying to load a fragment in your activity make sure that activity is in resume and not going to pause state.In pause state you may end up losing commit operation that is done.

You can use transaction.commitAllowingStateLoss() instead of transaction.commit() to load fragment

or

Create a boolean and check if activity is not going to onpause

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

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

then while loading fragment check

if(mIsResumed){
//load the your fragment
}
陌上青苔 2024-12-13 13:55:50

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

If you are doing some FragmentTransaction in onActivityResult what you can do you can set some boolean value inside onActivityResult then in onResume you can do your FragmentTransaction on the basis of the boolean value. Please refer the code below.

@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;
    }
}
谎言 2024-12-13 13:55:50

关于 @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();
    }
}

In regards to @Anthonyeef great answer, here is a sample code in 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();
    }
}
别念他 2024-12-13 13:55:50

这里抛出异常(在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();
        }
    }
}

The exception is threw here (In FragmentActivity):

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

In FragmentManager.popBackStatckImmediate()FragmentManager.checkStateLoss() is called firstly. That's the cause of IllegalStateException. See the implementation below:

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

I solve this problem simply by using a flag to mark Activity's current status. Here's my solution:

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();
        }
    }
}
客…行舟 2024-12-13 13:55:50

如果您使用 popBackStack() 或 popBackStackImmediate() 方法发生崩溃,请尝试修复:

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

这也对我有用。

If you have crash with popBackStack() or popBackStackImmediate() method please try fixt with:

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

This is worked for me as well.

高冷爸爸 2024-12-13 13:55:50

就我而言,我在名为 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())
    }

}

也许您只需要在代码之前在您正在执行的任何覆盖上添加“超级”。

In my case I got this error in an override method called onActivityResult. After digging I just figure out maybe I needed to call 'super' before.
I added it and it just worked

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

}

Maybe you just need to add a 'super' on any override you are doing before your code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文