非法状态异常:在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个回答

5

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

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

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

异常是由于当配置更改(设备旋转)时,活动被创建,主要片段从碎片管理器的历史记录中检索,并且同时片段已经引用了已销毁的活动。将实现更改为以下内容即可解决问题:
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);

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


5
如果您继承自FragmentActivity,则必须在onActivityResult()中调用超类:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    ...
}

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

1
很想知道为什么这样可以解决问题。 - Big McLargeHuge

4
在我的情况下,可能是最流畅和最简单的解决方案是避免在活动结果中从堆栈中弹出有问题的片段。因此,在我的onActivityResult()中更改此调用:
popMyFragmentAndMoveOn();

转换为:

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

在我的情况下提供了帮助。


3
感谢参考:解决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错误,但是如果在活动(activity)转移到后台后进行事务,则会丢失这些事务,类似于commitAllowingStateloss()。为了帮助解决这个问题,我们使用了一个名为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;
 }
}

3

当我在地图片段活动中按下返回按钮取消意图选择器时,我遇到了这个异常。我通过将onResume的代码(在那里我正在初始化片段)替换为onstart()来解决这个问题,现在应用程序可以正常工作。希望能有所帮助。


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

请勿将代码作为图像发布,而应使用代码格式化。 - Micer
1
是的,它帮助了我。这是适用于棉花糖设备的确切和正确的解决方案,我遇到了这个异常,并用这个简单的技巧解决了它。因此,我点了赞。 - sandhya sasane

2

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

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

或者,创建一个布尔值并检查活动是否不会进入onpause状态。

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

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

然后在加载片段时检查。
if(mIsResumed){
//load the your fragment
}

2

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

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

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

2

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

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

例如检查isChangingConfigurations()

即。

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

也可以查看此链接


当用户单击某个东西时(点击是触发事务提交的方式),我不知何故遇到了这个异常。这可能是什么原因?你的解决方案是什么? - android developer
@androiddeveloper,你在用户点击时还做了什么?不知何故,在提交事务之前,片段会保存其状态。 - Amol Desai
异常是在事务提交的确切行上抛出的。另外,我有一个奇怪的打字错误:我想说的是“在这里工作”,而不是“在这里这里”。 - android developer
@androiddeveloper 你说得对!但在提交事务之前,你是否会生成任何后台线程或其他东西? - Amol Desai
我不这么认为(抱歉,我不在办公室),但这有什么关系呢?这里都是UI相关的东西...如果我在后台线程上做了什么,我会在那里遇到异常,而且我不会将与UI相关的内容放在后台线程上,因为这太冒险了。 - android developer

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