为什么我的Activity的onResume方法会抛出“在onSaveInstanceState之后无法执行此操作”的异常?

30

我的活动使用ACTION_IMAGE_CAPTURE意图调用相机。如果相机活动成功返回,我在onActivityResult回调中设置一个标志,并根据标志的值在我的onResume中启动一个片段,以为捕获的图像添加标题。这似乎很好地工作。

我刚刚从“野外”获得了一个堆栈跟踪,抱怨我在onSaveInstanceState被调用后尝试提交片段事务。但是我正在我的onResume方法中进行提交!为什么安卓会抱怨这个?我在AndroidManifest.xml中已经设置了android:configChanges="orientation|keyboardHidden|keyboard|screenSize",因此方向变化不应该触发此操作....

这发生在运行4.0.4的三星Galaxy S3(SGH-i747)上。

以下是堆栈:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1314)
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1325)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:548)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:532)
    at com.Familiar.Android.FamiliarAppV1.AddPhotosActivity2.performFragmentTransition(AddPhotosActivity2.java:278)
    at com.Familiar.Android.FamiliarAppV1.AddPhotosActivity2.switchToCaptionsFragment(AddPhotosActivity2.java:438)
    at com.Familiar.Android.FamiliarAppV1.AddPhotosActivity2.onResume(AddPhotosActivity2.java:167)
    at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1158)
    at android.app.Activity.performResume(Activity.java:4544)
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2448)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2486)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1187)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4514)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
    at dalvik.system.NativeStart.main(Native Method)

非常感谢您的帮助和智慧。


3
有关此主题的更多信息,请参阅此博客文章 - Alex Lockwood
7个回答

56

认为我知道答案 - 我正在使用v4兼容库中的FragmentActivity,因此我需要在onResumeFragments而不是onResume中执行我的fragment事务。有人能够确认一下吗?


2
在我更改使用onResumeFragments而不是onResume来执行我的片段事务后,这些错误已经消失了 - 另一个数据点。我没有实现空片段解决方法,也没有需要。我会注意定期更新我的应用程序到最新的v4兼容库,因此Google可能已经在他们的端口修复了一些问题。我的活动在配置更改时不会重新启动 - 我在清单中覆盖此并自己处理配置更改。 - PacificSky
我不确定,这与onPostResume()是一样的吗? - StuStirling
@DiscoS2 是的。onResumeFragments()保证在活动状态恢复后被调用,因此onPostResume()也会被调用(因为onPostResume()总是在onResumeFragments()之后被调用)。 - Alex Lockwood
2
Google应该更好地记录这个。我还在等着Google在其指南中提到这一点! - Maxim Rahlis
2
在展示插页式广告后,我遇到了这个问题...旋转后,我的DialogFragment从onResume正确启动。 - stefan
谢谢,它对我有用......之前我在Activity的OnResume中执行Fragment事务,结果遇到了这个异常。 - Prashant

15

您可以使用方法 commitAllowingStateLoss()

但请注意,您可能会失去活动的状态,如Google的Android参考文档中所示, 以下是它们之间不同之处的解释:

  

与 commit() 类似,但允许在保存活动状态后执行提交。这很危险,因为如果需要从其状态恢复活动,则提交可能会丢失,因此只应用于其中 UI 状态随时可能出现意外变化的情况。

从我的经验来看,有时可能会导致 addToBackStack 方法无法正常工作,因此您需要在片段上手动添加它, 当然,状态也不会被保存(例如文本框文本等)


4

这对我很有效...我自己发现的...希望能帮到你!

1)不要有全局的“static”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();
    }
}

如果您只是使用getSupportFragmentManager().beginTransaction()而不是声明片段管理器,会发生什么情况?即使这样,错误仍然会发生。因此,这可能不是解决方案。 - Ali Kazi
@kurayami88 是的,这个解决方案对我有效。我仍然有一个全局静态的“fragmentManager”,但在“onCreate”中重新初始化变量就可以了。 - Wrichik Basu

2

更新 我认为我在这里找到了一个解释和解决方案:http://code.google.com/p/android/issues/detail?id=23096#c4 我实现了那里发布的“空白片段解决方法”,目前没有出现更多的非法状态异常。

我在我的Activity中像这样添加了不可见的状态片段:

@Override
protected void onCreate(final Bundle args) {
    ...
    if (args == null) {
        final FragmentManager fm = this.getSupportFragmentManager();
        final FragmentTransaction ft = fm.beginTransaction();
        final Fragment emptyFragmentWithCallback = new EmptyFragmentWithCallbackOnResume();
        ft.add(emptyFragmentWithCallback, EmptyFragmentWithCallbackOnResume.TAG);
        ft.commit();
    }

以下代码摘自上述链接:
public class EmptyFragmentWithCallbackOnResume extends Fragment {
OnFragmentAttachedListener mListener = null;

@Override
public void onAttach(SupportActivity activity) {
    super.onAttach(activity);
    try {
        mListener = (OnFragmentAttachedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString() + " must implement OnFragmentAttachedListener");
    }
}

@Override
public void onResume() {
    super.onResume();
    if (mListener != null) {
        mListener.OnFragmentAttached();
    }
}

public interface OnFragmentAttachedListener {
    public void OnFragmentAttached();
}
}

我希望能够将本应在onResume或onResumeFragments中直观执行的片段事务调用转移到我的自定义onFragmentAttached方法中,该方法由不可见状态片段调用。我根本不使用onResumeFragments,并且不在onResume中发出任何片段事务。

因此,总结一下。如果您正在使用支持库和片段,则基本上忘记onResume,忘记onResumeFragments,并根据上述解决方法实现自己的“onResume”。 这有点荒谬。


我无法确认。我遇到了完全相同的问题,即使我在onResumeFragments中发出片段事务。 这曾经是有效的,就像我在这里发布的那样:IllegalStateException - Fragment support library

似乎只有在4.0.3和4.0.4上才会出现错误。但它并不总是发生,也不会在我的模拟器中发生。

我正在使用支持库rev.10和API 16。 我在onResumeFragments中调用DialogFragment.show,并持续从某些随机用户那里收到这个荒谬的异常。我无法在本地重现它。


嗯——自从我开始使用onResumeFragments后,我就没有再看到这个问题了。你有调用super.onResumeFragments吗? - PacificSky
是的,我调用了onResumeFragments。这个问题显然只会偶尔出现,例如在方向更改时。我可以肯定地说,这个解决方法对我来说停止了这个问题。之前我每小时收到一次错误。 - phlebas
嗨phiebas,我不明白,我应该从onResumeFragments启动一个新的片段吗? - Maxrunner
嗨。我的简短回答是不,尽管我对这个问题的经验基于支持库rev10和API 16。我不知道这种行为最近是否已经被修复。我使用上述解决方法启动片段,因此我完全忽略了onResumeFragments并使用自定义的onFragmentAttached代替。这解决了我的问题。我已更新我的帖子,希望能更清楚地表达。 - phlebas

2

当我尝试在onActivityForResult()方法中显示片段时,我总是遇到这个问题,因此问题如下:

  1. 我的Activity已暂停和停止,这意味着已经调用了onSaveInstanceState()(对于早期的和后期的Honeycomb设备均是如此)。
  2. 在任何结果的情况下,我都会进行事务以显示/隐藏片段,从而导致这种IllegalStateException异常。

我所做的是:

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

1
我最初开发我的应用程序针对Android 2.2(SDK 8),使用支持v4库,当我开始在4.2(SDK 17)上使用它时,我遇到了与我的片段相同的问题。但是将我的清单更改为android:minSdkVersion =“8”android:targetSdkVersion =“17”,解决了我的问题。也许这也可以帮助您。

0

将此代码添加到您的Activity中:

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

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