我在市场上从我的应用程序获得用户报告,交付以下异常:

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有关,而我并不使用它。堆栈跟踪没有显示任何我自己的类,所以我不知道这个异常发生在哪里以及如何防止它。

为了记录:我有一个tabhost,在每个选项卡中都有一个在活动之间切换的ActivityGroup。


当前回答

这是2017年10月,谷歌制作了Android支持库与新事物称为生命周期组件。它为“onSaveInstanceState后不能执行此操作”的问题提供了一些新的思路。

简而言之:

使用生命周期组件来确定弹出片段的时间是否正确。

带解释的较长版本:

why this problem come out? It's because you are trying to use FragmentManager from your activity(which is going to hold your fragment I suppose?) to commit a transaction for you fragment. Usually this would look like you are trying to do some transaction for an up coming fragment, meanwhile the host activity already call savedInstanceState method(user may happen to touch the home button so the activity calls onStop(), in my case it's the reason) Usually this problem shouldn't happen -- we always try to load fragment into activity at the very beginning, like the onCreate() method is a perfect place for this. But sometimes this do happen, especially when you can't decide what fragment you will load to that activity, or you are trying to load fragment from an AsyncTask block(or anything will take a little time). The time, before the fragment transaction really happens, but after the activity's onCreate() method, user can do anything. If user press the home button, which triggers the activity's onSavedInstanceState() method, there would be a can not perform this action crash. If anyone want to see deeper in this issue, I suggest them to take a look at this blog post. It looks deep inside the source code layer and explain a lot about it. Also, it gives the reason that you shouldn't use the commitAllowingStateLoss() method to workaround this crash(trust me it offers nothing good for your code) How to fix this? Should I use commitAllowingStateLoss() method to load fragment? Nope you shouldn't; Should I override onSaveInstanceState method, ignore super method inside it? Nope you shouldn't; Should I use the magical isFinishing inside activity, to check if the host activity is at the right moment for fragment transaction? Yeah this looks like the right way to do. Take a look at what Lifecycle component can do. Basically, Google makes some implementation inside the AppCompatActivity class(and several other base class you should use in your project), which makes it a easier to determine current lifecycle state. Take a look back to our problem: why would this problem happen? It's because we do something at the wrong timing. So we try not to do it, and this problem will be gone. I code a little for my own project, here is what I do using LifeCycle. I code in 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()
    }

如上所示。我将检查主机活动的生命周期状态。对于支持库中的生命周期组件,这可能更具体。代码lifecyclecurrentState.isAtLeast(lifeccycle . state . resumed)的意思是,如果当前状态至少是onResume,不晚于它?这确保我的方法不会在其他生命状态(如onStop)期间执行。

Is it all done? Of course not. The code I have shown tells some new way to prevent application from crashing. But if it do go to the state of onStop, that line of code wont do things and thus show nothing on your screen. When users come back to the application, they will see an empty screen, that's the empty host activity showing no fragments at all. It's bad experience(yeah a little bit better than a crash). So here I wish there could be something nicer: app won't crash if it comes to life state later than onResume, the transaction method is life state aware; besides, the activity will try continue to finished that fragment transaction action, after the user come back to our app. I add something more to this method:

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

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

其他回答

我找到了一个解决这类问题的下流方法。如果您出于某种原因(我有时间限制的原因)仍然希望保留ActivityGroups,您只需实现即可

public void onBackPressed() {}

在Activity中做一些回显代码。即使旧设备上没有这样的方法,这个方法也会被新设备调用。

在我的情况下,我在一个叫做onActivityResult的覆盖方法中得到了这个错误。在挖掘之后,我才发现也许我之前需要打电话给“超级”。 我加进去了,效果很好

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”。

简单有效的解决方案:

遵循简单的步骤

步骤

步骤1:在各自的片段中覆盖onSaveInstanceState状态。并从中移除super method。

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

2 .使用 fragmentTransaction.commitAllowingStateLoss ();

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

不要使用commitAllowingStateLoss(),它只应该用于用户的UI状态可以意外改变的情况。

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

如果事务发生在parentFragment的ChildFragmentManager中,则使用 在外面检查parentFragment.isResume()。

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

如果你继承FragmentActivity,你必须调用onActivityResult()中的超类:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    ...
}

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