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

nha*_*man 472 android illegalstateexception android-fragments android-viewpager fragmenttransaction

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

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

显然它与FragmentManager有关,我不使用它.stacktrace没有显示我自己的任何类,所以我不知道这个异常发生在哪里以及如何防止它.

对于记录:我有一个tabhost,并且在每个选项卡中都有一个ActivityGroup在Activities之间切换.

Ovi*_*tcu 705

在这里查看我的答案.基本上我只需要:

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

不要super()saveInstanceState方法进行调用.这搞砸了......

这是支持包中的已知错误.

如果您需要保存实例并向您添加内容,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)

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

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

添加或执行FragmentTransaction导致的时间Exception.

  • 你应该使用commitAllowingStateLoss()而不是commit() (360认同)
  • 关于'commitAllowingStateLoss' - />"这是危险的,因为如果活动需要稍后从其状态恢复,则提交可能会丢失,因此这应该仅用于UI状态可以意外更改的情况用户." (18认同)
  • 关于commitAllowingStateLoss()的这个评论本身就是一个答案 - 你应该发布它. (17认同)
  • 如果我查看`popBackStackImmediate`的v4源代码,如果状态已保存,它会立即失败.以前使用`commitAllowingStateLoss`添加片段不起任何作用.我的测试表明这是真的.它对此特定异常没有影响.我们需要的是一个`popBackStackImmediateAllowingStateLoss`方法. (10认同)
  • @DanieleB是的,我在这里发了一个答案.但我实际上通过使用Otto消息总线找到了更好的解决方案:将片段注册为订阅者并侦听来自总线的异步结果.暂停时取消注册并在恢复时重新注册.async在完成和片段暂停时也需要一个Produce方法.当我有时间时,我会更详细地更新我的答案. (3认同)
  • @ IteratioN7T问题在于当应用程序不在前台时从后台作业回调后进行片段事务.最好的方法是避免这样做,因为这是一种不好的做法. (2认同)

Syn*_*sso 120

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

如果已保存会话状态popBackStack,则此方法调用将始终失败IllegalStateException.检查来源.没有什么可以阻止抛出此异常.

  • 删除呼叫super.onSaveInstanceState无济于事.
  • 创建片段commitAllowingStateLoss无济于事.

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

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

这是我为解决它所做的事情:

由于无法避免IllegalStateException回调,因此请抓住并忽略它.

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

这足以阻止应用程序崩溃.但现在用户将恢复应用程序并看到他们认为按下的按钮根本没有被按下(他们认为).表单片段仍在显示!

要解决此问题,请在创建对话框时,进行一些状态以指示进程已启动.

progressDialog.show(fragmentManager, TAG);
submitPressed = true;
Run Code Online (Sandbox Code Playgroud)

并将此状态保存在捆绑中.

@Override
public void onSaveInstanceState(Bundle outState) {
    ...
    outState.putBoolean(SUBMIT_PRESSED, submitPressed);
}
Run Code Online (Sandbox Code Playgroud)

别忘了再把它装回去 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 有趣的阅​​读在这里:http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html (3认同)
  • 绝对棒极了。这应该是公认的答案。非常感谢!也许我会添加submitPressed = false; 在 popBackStackInmediate 之后。 (2认同)

Nas*_*kov 55

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

  • !isFinishing() && !isDestroyed() 对我不起作用。 (2认同)

Ant*_*eef 23

这是2017年10月,谷歌使用新的东西调用生命周期组件制作Android支持库.它为此问题提供了一些新的想法'在onSaveInstanceState之后无法执行此操作'问题.

简而言之:

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

更长版本说明:

  • 为什么这个问题出来了?

    这是因为你试图FragmentManager从你的活动中使用(我想假装你的片段?)为你的片段提交一个事务.通常这看起来就像你正在尝试为即将到来的片段做一些事务,同时主机活动已经调用savedInstanceState方法(用户可能碰巧触摸主页按钮,所以活动调用onStop(),在我的情况下,这是原因)

    通常这个问题不应该发生 - 我们总是尝试在最开始时将片段加载到活动中,就像onCreate()方法是一个完美的地方.但有时会发生这种情况,尤其是当您无法确定要加载到该活动的片段时,或者您正在尝试从AsyncTask块中加载片段(或者任何事情需要一些时间).在片段事务真正发生之前的时间,但在活动的onCreate()方法之后,用户可以做任何事情.如果用户按下主页按钮,触发活动的onSavedInstanceState()方法,则会can not perform this action发生崩溃.

    如果有人想在这个问题看得更深,我建议他们看看这个博客帖子.它深入了解源代码层并对其进行了大量解释.此外,它给出了您不应该使用该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()
    }
Run Code Online (Sandbox Code Playgroud)

正如我在上面所示.我将检查主机活动的生命周期状态.借助支持库中的Lifecycle组件,这可能更具体.代码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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 使用Java而不是Kotlin会很好 (8认同)
  • 如果只恢复一个片段,为什么“FragmentDispatcher”的实现使用一个列表来存储待处理的片段? (2认同)

Jed*_*Jed 21

以下是此问题的不同解决方案.

使用私有成员变量,您可以将返回的数据设置为intent,然后可以在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 根据您正在进行的不允许的操作,这可能需要转移到比onResume()更晚的时刻.对于insatcne,如果FragmentTransaction.commit()是问题,则需要进入onPostResume(). (7认同)
  • 对我而言,这是因为我没有调用`super.onActivityResult()`. (7认同)

Vin*_*yak 19

短而有效的解决方案:

遵循简单步骤

脚步

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

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  
Run Code Online (Sandbox Code Playgroud)

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

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


Eri*_*ein 12

请注意,使用transaction.commitAllowingStateLoss()可能会给用户带来糟糕的体验.有关抛出此异常的原因的详细信息,请参阅此文章.

  • 这不能解答问题,您必须为问题提供有效答案 (6认同)

sab*_*der 10

我找到了解决这类问题的肮脏方案.如果您仍然想要保留您ActivityGroups的原因(我有时间限制的原因),您只需实施

public void onBackPressed() {}
Run Code Online (Sandbox Code Playgroud)

在你的Activity,并back在那里做一些代码.即使旧设备上没有这样的方法,这个方法也会被更新的方法调用.


Cha*_*ler 6

不要使用commitAllowingStateLoss(),它只应用于UI状态可以在用户上意外更改的情况.

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

如果事务发生在parentFragment的ChildFragmentManager中,请使用 parentFragment.isResume() outside来检查.

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
Run Code Online (Sandbox Code Playgroud)


Min*_*amy 5

我有类似的问题,场景是这样的:

  • 我的活动是添加/替换列表片段.
  • 每个列表片段都有一个对活动的引用,以便在单击列表项时通知活动(观察者模式).
  • 每个列表片段调用setRetainInstance(true); 在其onCreate方法中.

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

抛出异常是因为when配置更改(设备已旋转),活动已创建,主片段从片段管理器的历史记录中检索,同时片段已经具有对已销毁活动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);
Run Code Online (Sandbox Code Playgroud)

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


Law*_*oot 5

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

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    ...
}
Run Code Online (Sandbox Code Playgroud)

如果不这样做,并尝试在该方法中显示一个片段对话框,则可能会得到OP的IllegalStateException。(说实话,我不太理解为什么超级调用解决了这个问题。onActivityResult()在之前被调用了onResume(),因此仍然不允许显示片段对话框。)


Ivo*_*nov 5

片段交易不应在之后执行Activity.onStop() 检查您是否没有任何可以在 之后执行事务的回调onStop()。最好是解决问题的原因,而不是试图用类似的方法来解决问题.commitAllowingStateLoss()


归档时间:

查看次数:

299562 次

最近记录:

6 年,1 月 前