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

dco*_*ool 350 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)  
Run Code Online (Sandbox Code Playgroud)

Ovi*_*tcu 451

这是我到目前为止遇到的最愚蠢的错误.我有一个Fragment应用程序为正常使用API <11,和Force ClosingAPI> 11.

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

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

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

编辑:经过一些研究,这是支持包中的一个已知错误.

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

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);
}
Run Code Online (Sandbox Code Playgroud)

编辑2:如果您Activity在后台工作后尝试执行交易,也可能发生这种情况.为避免这种情况,你应该使用commitAllowingStateLoss()

编辑3:上述解决方案正在解决早期的support.v4库中的问题.但是,如果你仍然有这方面的问题,你还必须阅读@AlexLockwood的博客:片段交易和活动状态损失

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

  • 永远不会 commit()onPause()蜂窝前和onStop()后蜂窝上进行交易
  • Activity生命周期方法中提交事务时要小心.使用 onCreate(),onResumeFragments()onPostResume()
  • 避免在异步回调方法中执行事务
  • 使用commitAllowingStateLoss()仅作为最后的手段

  • 你应该使用commitAllowingStateLoss()而不是commit() (96认同)
  • @meh`commitAllowingStateLoss()`只能避免异常.它不能保护您的应用程序免受意外状态丢失.请参阅[**博客文章**](http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html). (55认同)
  • 因此,不在onSaveInstanceState中调用super将阻止FragmentManager保存所有片段的状态并恢复它们.这可能会导致轮换问题.另外,我刚刚尝试了将垃圾放入捆绑包中的另一件事,这对我没什么影响.不知道怎么回事 - 你在支持包中引用的bug是NullPointerException,并且看起来不像IllegalStateException ... (7认同)
  • @AlexLockwood所以从博客文章中我们可以了解到我们应该在片段内部进行所有网络调用(并在需要时显示一些临时进度ui),这是我们可以避免因为提交而发生此异常的唯一方法正在调用一些异步方法之后调用. (2认同)

gun*_*nar 75

在Android源代码中查看导致此问题的原因是,FragmentManagerImpl类中的标志mStateSaved (Activity中可用的实例)的值为true.在调用后保存后端堆栈(saveAllState)时,它设置为true Activity#onSaveInstanceState.然后从ActivityThread呼叫不使用从可用的复位方法重置此标志FragmentManagerImpl#noteStateNotSaved()dispatch().

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

好方法

在此之前:我会宣传Alex Lockwood的文章.然后,从我到目前为止所做的:

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

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

  2. 在事务提交之后(您刚刚调用commit()),请拨打电话FragmentManager.executePendingTransactions().

不推荐的方式:

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

  2. 覆盖onBackPressed并仅在那里呼叫finish().如果您的应用程序不使用Fragments API,这应该没问题; 就像在super.onBackPressed打电话FragmentManager#popBackStackImmediate().

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

下面是使用反射的代码:

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

干杯!


Fun*_*onk 34

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

这可能发生的一个原因是,如果您在活动停止时离开AsyncTask(或Thread)运行.

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

  • 嘿Funk,我在这里有一个问题,如果该活动或片段已被停止,为什么可以在该活动或片段上调用onBackPressed.上面的异常似乎是从某些UI事件(即按下BACK键)生成的,但是我无法找到Async Task和Back键之间的关系. (2认同)
  • @Buffalo这绝对不是问题的解决方案.你应该总是调用`super.onSaveInstanceState()`. (2认同)

小智 27

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

  • 调用onPostResume()可确保调用onResumeFragments(),对我而言,这是理想的解决方案. (6认同)

Bri*_*ley 19

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

dismissAllowingStateLoss()
Run Code Online (Sandbox Code Playgroud)

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

  • 这是我的确切问题!先生,您真聪明! (2认同)

Bas*_*ous 17

短而有效的解决方案:

遵循简单步骤:

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

@Override
public void onSaveInstanceState(Bundle outState) {
};
Run Code Online (Sandbox Code Playgroud)

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

fragmentTransaction.commitAllowingStateLoss();
Run Code Online (Sandbox Code Playgroud)

  • 删除super()是不安全的,之后你会丢失其他数据! (6认同)

Jam*_*mal 12

我认为Lifecycle状态可以帮助防止从Android支持lib v26.1.0开始的这种崩溃,你可以进行以下检查:

if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)){
  // Do fragment's transaction commit
}
Run Code Online (Sandbox Code Playgroud)

或者你可以尝试:

Fragment.isStateSaved()
Run Code Online (Sandbox Code Playgroud)

更多信息请访问 https://developer.android.com/reference/android/support/v4/app/Fragment.html#isStateSaved()


小智 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么首先要有一个类级别的片段管理器? (2认同)

Arr*_*ray 6

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

  1. 我的Activity暂停并停止,这意味着已经调用onSaveInstanceState()(对于蜂窝前蜂窝和蜂窝后蜂窝设备).
  2. 如果有任何结果我将事务显示/隐藏片段,这会导致此IllegalStateException.

我接下来做的是:

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


小智 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);
} 
Run Code Online (Sandbox Code Playgroud)