获取异常“IllegalStateException:onSaveInstanceState后无法执行此操作”

时间:2011-09-19 09:50:07

标签: android illegalstateexception

我有一个Live Android应用程序,从市场上我收到了以下堆栈跟踪,我不知道为什么它发生在应用程序代码中没有发生,但它是由应用程序中的某些或其他事件引起的(假设)

我没有使用Fragments,仍然有FragmentManager的引用。 如果任何机构可以对某些隐藏的事实有所了解,以避免这类问题:

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.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
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.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:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
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:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)  

31 个答案:

答案 0 :(得分:443)

这是我到目前为止遇到的最愚蠢的错误。我的 Fragment 应用程序非常适用于 API< 11 Force Closing API> 11

Activity的调用中,我真的无法弄清楚他们在saveInstance生命周期内发生了什么变化,但我在这里解决了这个问题:

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

我只是不打电话给.super(),一切都很好。我希望这会为你节省一些时间。

编辑:经过一些研究,这是支持包中已知的bug

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

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

EDIT2:如果您在后台Activity消失后尝试执行交易,也可能会发生这种情况。为避免这种情况,您应该使用commitAllowingStateLoss()

EDIT3:上述解决方案正在修复早期的support.v4库中的问题。但是,如果您仍然遇到此问题,必须还请阅读@AlexLockwood的博客:Fragment Transactions & Activity State Loss

博客文章摘要(但我强烈建议您阅读):

  • 从事<{1}}之前的事件<{1}}前蜂窝事件,commit()事后蜂窝事件
  • onPause()生命周期方法中提交事务时要小心。 使用 onStop()ActivityonCreate()
  • 避免在异步回调方法中执行事务
  • 仅将onResumeFragments()作为最后手段使用

答案 1 :(得分:73)

在Android源代码中查看导致此问题的原因是FragmentManagerImpl类中的标志mStateSaved(Activity中可用的实例)的值为true。当从Activity#onSaveInstanceState调用后保存后台堆栈(saveAllState)时,它设置为true。 之后来自ActivityThread的调用不会使用FragmentManagerImpl#noteStateNotSaved()dispatch()中的可用重置方法重置此标记。

我看到它的方式有一些可用的修复程序,具体取决于您的应用程序正在执行和使用的内容:

好方法

其他事情:我会做广告Alex Lockwood article。然后,从我到目前为止所做的:

  1. 对于不需要保留任何状态信息的片段和活动,请致电commitAllowStateLoss。取自文档:

      

    允许在保存活动状态后执行提交。这是危险的,因为如果活动需要稍后从其状态恢复,则提交可能会丢失,因此这应该仅用于UI状态可以在用户上意外更改的情况。如果片段显示只读信息,我想这是可以的。或者即使它们显示可编辑信息,也可以使用回调方法保留已编辑的信息。

  2. 在提交交易之后(您刚刚拨打commit()),请拨打FragmentManager.executePendingTransactions()

  3. 不推荐的方式:

    1. 正如上面提到的Ovidiu Latcu,请不要致电super.onSaveInstanceState()。但这意味着你将失去活动的整个状态以及碎片状态。

    2. 覆盖onBackPressed,然后只调用finish()。如果您的应用程序不使用Fragments API,这应该没问题;与在super.onBackPressed中一样,可以调用FragmentManager#popBackStackImmediate()

    3. 如果您同时使用Fragments API并且您的活动状态非常重要,那么您可以尝试使用反射API FragmentManagerImpl#noteStateNotSaved()进行调用。但这是一个黑客,或者可以说这是一个解决方法。我不喜欢它,但在我的情况下它是完全可以接受的,因为我有遗留应用程序的代码使用已弃用的代码(TabActivity和隐式LocalActivityManager)。

    4. 以下是使用反射的代码:

      @Override
      protected void onSaveInstanceState(Bundle outState) {
          super.onSaveInstanceState(outState);
          invokeFragmentManagerNoteStateNotSaved();
      }
      
      @SuppressWarnings({ "rawtypes", "unchecked" })
      private void invokeFragmentManagerNoteStateNotSaved() {
          /**
           * For post-Honeycomb devices
           */
          if (Build.VERSION.SDK_INT < 11) {
              return;
          }
          try {
              Class cls = getClass();
              do {
                  cls = cls.getSuperclass();
              } while (!"Activity".equals(cls.getSimpleName()));
              Field fragmentMgrField = cls.getDeclaredField("mFragments");
              fragmentMgrField.setAccessible(true);
      
              Object fragmentMgr = fragmentMgrField.get(this);
              cls = fragmentMgr.getClass();
      
              Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
              noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
              Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
          } catch (Exception ex) {
              Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
          }
      }
      

      干杯!

答案 2 :(得分:34)

如果在调用片段活动onSaveInstanceState()后尝试执行片段转换,则会发生此类异常。

这种情况可能发生的一个原因是,如果您在某项活动停止时让AsyncTask(或Thread)继续运行。

如果系统回收资源的活动并稍后重新创建,则调用onSaveInstanceState()之后的任何转换都可能会丢失。

答案 3 :(得分:27)

在显示片段之前,只需调用 super.onPostResume(),或在调用super.onPostResume()之后在onPostResume()方法中移动代码。这解决了问题!

答案 4 :(得分:18)

在屏幕锁定\消隐并且保存了Activity +对话框的实例状态后,在对话框片段上调用dismiss()时也会发生这种情况。绕过这个电话:

dismissAllowingStateLoss()

从字面上看,每次我都在解除对话时,我不再关心它的状态,所以这样做是可以的 - 你实际上并没有失去任何状态。

答案 5 :(得分:17)

简短而有效的解决方案:

遵循简单步骤:

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

@Override
public void onSaveInstanceState(Bundle outState) {
};

第2步:使用CommitAllowingStateLoss();而不是commit();片段操作。

fragmentTransaction.commitAllowingStateLoss();

答案 6 :(得分:10)

我认为Lifecycle状态有助于防止此类崩溃从Android支持lib v26.1.0开始,您可以进行以下检查:

if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)){
  // Do fragment's transaction commit
}

或者您可以尝试:

Fragment.isStateSaved()

此处提供更多信息 https://developer.android.com/reference/android/support/v4/app/Fragment.html#isStateSaved()

答案 7 :(得分:7)

这对我有用......我自己发现了......希望它对你有帮助!

1)没有全局“静态”FragmentManager / FragmentTransaction。

2)onCreate,总是再次初始化FragmentManager!

以下示例: -

public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mSavedInstanceState = savedInstanceState;
    setDefaultFragments();
}

protected void setDefaultFragments() {
    fragmentManager = getSupportFragmentManager();
    //check if on orientation change.. do not re-add fragments!
    if(mSavedInstanceState == null) {
        //instantiate the fragment manager

        fragmentTransaction = fragmentManager.beginTransaction();

        //the navigation fragments
        NavigationFragment navFrag = new NavigationFragment();
        ToolbarFragment toolFrag = new ToolbarFragment();

        fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
        fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
        fragmentTransaction.commitAllowingStateLoss();

        //add own fragment to the nav (abstract method)
        setOwnFragment();
    }
}

答案 8 :(得分:6)

当我试图在onActivityForResult()方法中显示片段时,我总是得到这个,所以接下来是问题:

  1. 我的活动暂停并停止,这意味着已经调用了onSaveInstanceState()(对于蜂窝前蜂窝和后蜂窝设备)。
  2. 如果有任何结果,我将事务显示/隐藏片段,这会导致此IllegalStateException。
  3. 我接下来做了什么:

    1. 确定我想要的操作是否已完成的附加值(例如从camere拍摄照片 - isPhotoTaken) - 它可以是布尔值或整数值,具体取决于您需要多少不同的交易。
    2. 在重写onResumeFragments()方法时,我检查了我的值,并在完成了我需要的片段事务之后。在这种情况下,在onSaveInstanceState之后没有执行commit(),因为onResumeFragments()方法返回了状态。

答案 9 :(得分:5)

我用onconfigurationchanged解决了这个问题。诀窍是根据android活动生命周期,当你明确调用一个intent(相机意图,或任何其他一个);在此情况下,活动暂停并调用onsavedInstance。将设备旋转到不同于活动活动期间的其他位置时;执行片段提交等片段操作会导致非法状态异常。关于它有很多抱怨。这是关于android活动生命周期管理和正确方法调用的东西。 为了解决这个问题我做了这个: 1 - 覆盖活动的onsavedInstance方法,并确定当前屏幕方向(纵向或横向),然后在活动暂停前将屏幕方向设置为该方向。这样一种活动,你可以锁定活动的屏幕旋转,以防它被另一个活动旋转。 2 - 然后,覆盖onresume活动方法,并将你的方向模式设置为传感器,以便在调用onsaved方法后,它将再次调用配置来正确处理旋转。

您可以将此代码复制/粘贴到您的活动中以处理它:

@Override
protected void onSaveInstanceState(Bundle outState) {       
    super.onSaveInstanceState(outState);

    Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
    int orientation =this.getDisplayOrientation();
    //Lock the screen orientation to the current display orientation : Landscape or Potrait
    this.setRequestedOrientation(orientation);
}

//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device 
public int getDisplayOrientation() {
    Display getOrient = getWindowManager().getDefaultDisplay();

    int orientation = getOrient.getOrientation();

    // Sometimes you may get undefined orientation Value is 0
    // simple logic solves the problem compare the screen
    // X,Y Co-ordinates and determine the Orientation in such cases
    if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        Configuration config = getResources().getConfiguration();
        orientation = config.orientation;

        if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        // if height and widht of screen are equal then
        // it is square orientation
            if (getOrient.getWidth() == getOrient.getHeight()) {
                orientation = Configuration.ORIENTATION_SQUARE;
            } else { //if widht is less than height than it is portrait
                if (getOrient.getWidth() < getOrient.getHeight()) {
                    orientation = Configuration.ORIENTATION_PORTRAIT;
                } else { // if it is not any of the above it will defineitly be landscape
                    orientation = Configuration.ORIENTATION_LANDSCAPE;
                }
            }
        }
    }
    return orientation; // return value 1 is portrait and 2 is Landscape Mode
}

@Override
public void onResume() {
    super.onResume();
    Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
} 

答案 10 :(得分:4)

我对这个问题的解决方案是

在片段添加方法中:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap);
    guideMap = guideMapFragment.getMap();
    ...
}

@Override
public void onDestroyView() {
    SherlockFragmentActivity a = getSherlockActivity();
    if (a != null && guideMapFragment != null) {
        try {
            Log.i(LOGTAG, "Removing map fragment");
            a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit();
            guideMapFragment = null;
        } catch(IllegalStateException e) {
            Log.i(LOGTAG, "IllegalStateException on exit");
        }
    }
    super.onDestroyView();
}

可能不好,但找不到更好的东西。

答案 11 :(得分:4)

我的应用程序中遇到了同样的问题。我已经解决了这个问题,只是在前一个类上调用super.onBackPressed();并使用该片段调用当前类的commitAllowingStateLoss()

答案 12 :(得分:4)

我有同样的问题,得到IllegalStateException,但用commitAllowingStateLoss()替换我对commit()的所有调用都没有帮助。

罪魁祸首是对DialogFragment.show()的调用。

我用

包围它
try {
    dialog.show(transaction, "blah blah");
}
catch(IllegalStateException e) {
    return;
}

就是这样做的。好的,我没有显示对话框,但在这种情况下很好。

这是我应用程序中唯一一个第一次调用FragmentManager.beginTransaction()的地方,但从未调用commit(),所以当我查找“commit()”时我没有找到它。

有趣的是,用户永远不会离开应用程序。相反,杀手是一个出现的AdMob插页式广告。

答案 13 :(得分:3)

如果用户旋转屏幕以便它可以加载与新方向相关联的资源,则会调用

onSaveInstance。

此用户可能会旋转屏幕,然后按后退按钮(因为此用户也可能在使用您的应用时弄乱了手机)

答案 14 :(得分:2)

http://chris-alexander.co.uk/on-engineering/dev/android-fragments-within-fragments/

制品。 fragment.isResumed()检查使用onSaveInstanceState方法帮助我使用onDestroyView。

答案 15 :(得分:2)

每当您尝试加载片段但活动已将其状态更改为onPause()时,就会发生这种情况。例如,当您尝试获取数据并将其加载到活动但是在用户点击某些活动时按钮并已移至下一个活动。

您可以通过两种方式解决此问题

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

答案 16 :(得分:2)

同样的问题,在对所有文章,博客和stackoverflow进行了一天的分析之后,我找到了一个简单的解决方案。根本不使用savedInstanceState,这是具有一行代码的条件。片段代码:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
    .....

答案 17 :(得分:0)

在我的情况下,出现相同的错误异常,我把&#34; onBackPressed()&#34;在runnable中(您可以使用任何视图):

myView.post(new Runnable() {
                    @Override
                    public void run() {
                        onBackPressed()
                    }
                });

我不明白为什么,但它有效!

答案 18 :(得分:0)

解决该问题的另一种生命周期方法是将最新发布的生命周期-ktx 与 kotlin 结合使用。

lifecycleScope.launchWhenResumed {
    // your code with fragment or dialogfragment
}

这个闭包会在resume状态后运行,所以即使这个方法在之后调用 停止,下次简历来的时候会安全地执行。

您也可以选择喜欢

lifecycleScope.launchWhenCreated
// or
lifecycleScope.launchWhenStarted

适合您的情况。

destroy 来的时候代码会被取消。

Google 文档链接: https://developer.android.com/kotlin/ktx#lifecycle

答案 19 :(得分:0)

我注意到一些非常有趣的事情。我在我的应用程序中选择打开手机的图库,设备询问要使用的应用程序,在那里我点击对话框的灰色区域,看到了这个问题。我注意到我的活动是如何从onPause,onSaveInstanceState回到onResume,它没有碰巧访问onCreateView。我在onResume做交易。所以我最终做的是设置一个标志被onPause否定,但是在onCreateView上是真的。如果该标志为真onResume然后执行onCommit,否则commitAllowingStateLoss。我可以继续浪费这么多时间,但我想检查生命周期。我有一个设备是sdkversion 23,我没有得到这个问题,但我有另一个是21,我看到它。

答案 20 :(得分:0)

谢谢@gunar,但我认为还有更好的方法。

根据文件:

 * If you are committing a single transaction that does not modify the
 * fragment back stack, strongly consider using
 * {@link FragmentTransaction#commitNow()} instead. This can help avoid
 * unwanted side effects when other code in your app has pending committed
 * transactions that expect different timing.
 *
 * @return Returns true if there were any pending transactions to be
 * executed.
 */
public abstract boolean executePendingTransactions();

因此请使用commitNow替换:

fragmentTransaction.commit();
FragmentManager.executePendingTransactions()

答案 21 :(得分:0)

您可能正在调用fragmentManager.popBackStackImmediate();当活动暂停时。活动尚未完成但暂停,而不是前台。您需要在popBackStackImmediate()之前检查活动是否暂停。

答案 22 :(得分:0)

这在Android 4.2中以及在支持库的源代码中已得到修复。[*]

有关原因(和解决方法)的详细信息,请参阅Google错误报告: http://code.google.com/p/android/issues/detail?id=19917

如果您正在使用支持库,那么您不必担心此错误(很长时间)[*]。但是,如果您直接使用API​​(即不使用支持库的FragmentManager)并在Android 4.2下面定位API,那么您将需要尝试其中一种解决方法。

[*]在撰写本文时,Android SDK Manager仍在发布展示此错误的旧版本。

编辑我将在这里添加一些澄清,因为我显然已经混淆了对这个答案投票的人。

几种不同(但相关)的情况会导致抛出此异常。我上面的回答是指问题中讨论的具体实例,即随后修复的Android中的错误。如果你因为另一个原因而得到这个异常,那是因为你不应该添加/删除片段(在片段状态被保存之后)。如果您处于这种情况,那么“Nested Fragments - IllegalStateException “Can not perform this action after onSaveInstanceState””可能对您有用。

答案 23 :(得分:0)

我发现如果另一个应用程序是对话框类型并允许将触摸发送到后台应用程序,那么几乎任何后台应用程序都会因此错误而崩溃。 我认为如果保存或恢复实例,每次执行事务时都需要检查。

答案 24 :(得分:0)

我的用例:我在片段中使用了监听器来通知活动发生了一些事情。我在回调方法上做了新的片段提交。这在第一次完美无缺。但是在方向更改时,将使用已保存的实例状态重新创建活动。在那种情况下,不再创建片段意味着片段具有旧的被破坏活动的监听器。任何方式回调方法将在动作时触发。它会破坏导致问题的活动。解决方案是使用当前的实时活动重置片段中的侦听器。这解决了这个问题。

答案 25 :(得分:0)

好吧,在尝试了所有上述解决方案后没有成功(因为基本上我没有交易)。

在我的情况下,我使用AlertDialogs和ProgressDialog作为片段,有时在轮换时,在请求FragmentManager时,错误会上升。

我找到了一个混合了许多类似帖子的解决方法:

它是一个3步解决方案,全部在你的FragmentActivity上完成(在这种情况下,它叫做GenericActivity):

private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    //To avoid bug for fragments: Step 2 of 3
    activity = new WeakReference<GenericActivity>(this);
}

@Override
public FragmentManager getSupportFragmentManager(){
    //To avoid bug for fragments: Step 3 of 3
    if (this == activity.get()) {
        return super.getSupportFragmentManager();
    }
    return activity.get().getSupportFragmentManager();
}

答案 26 :(得分:0)

在研究了一下之后,解决这个问题的方法是在onresume中进行片段提交。

来源:https://wenchaojames.wordpress.com/2013/01/12/illegalstateexception-from-onactivityresult/

答案 27 :(得分:0)

当我在一个片段中使用startactivity时,我将得到此异常;

当我改为使用startactivityforresult时,异常消失了:)

所以解决它的简单方法是使用startActivityForResult api:)

答案 28 :(得分:0)

当我按下按钮取消我的地图片段活动上的意图选择器时,我收到了此异常。 我通过将onResume()(我正在初始化片段并提交事务)的代码替换为onStart()来解决这个问题,现在应用程序运行正常。 希望它有所帮助。

答案 29 :(得分:-1)

你可以在popBackStackImmediate之前使用FragmentActivity.onStart

像这样:

public void backStackFragment() {
    this.start();
    getFragmentManager().popBackStackImmediate();
}

public void start(){
    FragmentActivity a = getActivity();
    if(a instanceof DepositPlanPadActivity){
      ((DepositPlanPadActivity)a).onStart();
    }
    if(a instanceof SmallChangePlanPad){
            ((SmallChangePlanPad)a).onStart();
        }
        if(a instanceof UserCenterActivity){
            ((UserCenterActivity)a).onStart();
        }
    }

http://jorryliu.blogspot.com/2014/09/illegalstateexception-can-not-perform.html

答案 30 :(得分:-2)

我认为在这些操作之前调用FragmentActivity.onStateNotSaved()可能是目前最好的选择。