非法状态异常:在ViewPager的onSaveInstanceState之后无法执行此操作。

570

我从应用市场获取用户报告,显示以下异常:

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,在活动之间进行切换。


2
我发现了一个讨论相同问题的问题,但那里也没有解决方案。https://dev59.com/qms05IYBdhLWcg3wD90B - nhaarman
3
尽管你没有使用“FragmentManager”,但Honeycomb系统肯定在使用它。这是否发生在真正的Honeycomb平板电脑上?或者可能是有人在手机上运行了一个被黑客入侵的Honeycomb版本,导致出现了问题? - CommonsWare
1
我不知道。这是我在市场开发者控制台中得到的唯一信息,用户消息也没有任何有用的信息。 - nhaarman
这是我的解决方案: https://dev59.com/A-o6XIcBkEYKwwoYOR0q#31016553 希望有人能解决这个问题。 - nobjta_9x_tq
对我来说最好的工作答案是这个:https://dev59.com/dpjga4cB1Zd3GeqPNKNh#62554599(也不会损失状态) - Beko
显示剩余4条评论
36个回答

2

getFragmentManager()更改为getChildFragmentManager()。不要使用父级FragmentManager,尽量使用自身。


2

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://dev59.com/qG025IYBdhLWcg3wyJCV#59158254
        addToBackStack(tag)
    }?.commitAllowingStateLoss()
}

使用方法:

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

你可以在Fragment之前删除() -> - Claire
@Claire,谢谢!你的意思是改成fragment: Fragment吗?是的,我尝试了这种变体,但在这种情况下,无论何时都会创建一个片段(即使当fragmentManager == null时,但我没有遇到这种情况)。我更新了答案,并在addToBackStack()中将null更改为tag。 - CoolMind

2
在我的情况下,我在一个名为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())
    }

}

也许你只需要在你的代码之前添加一个“super”,来覆盖任何你正在执行的操作。最初的回答。

2

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

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

这对我也起作用了。


请注意,它需要API 26及以上版本。 - Itay Feldman

2

异常在这里抛出(在FragmentActivity中):

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

FragmentManager.popBackStackImmediate()方法中,首先会调用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();
        }
    }
}

1

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

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


1

我知道@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()非常有帮助,从此再也没有遇到过fragment问题。

总之,在这里接受的答案可能适用于Nougat之前的Android版本。

这可能会为某些人节省几个小时的搜索时间。 愉快的编码。 <3 万岁


1
为了规避这个问题,我们可以使用在2018年Google I/O中推出的导航架构组件。导航架构组件简化了Android应用程序中导航的实现。

这还不够好,而且有漏洞(深度链接处理不佳,无法保存状态显示/隐藏片段以及一些重要问题仍然未解决)。 - Hamid Zandi

0
我最终创建了一个基础片段,并使应用中的所有片段都扩展它。
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


0

在你的活动中添加这个

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接