Android Fragment 和方向更改导致:IllegalStateException: Can not perform this action after onSaveInstanceStat

Posted

技术标签:

【中文标题】Android Fragment 和方向更改导致:IllegalStateException: Can not perform this action after onSaveInstanceState【英文标题】:Android Fragment and orientation change causing: IllegalStateException: Can not perform this action after onSaveInstanceState 【发布时间】:2012-07-25 23:02:57 【问题描述】:

每当我加载了一个片段的主要活动并且用户启动一个新活动、切换设备的方向并返回到主要活动时,我都会收到此错误。

@Override
public void onCreate(Bundle savedInstanceState) 
    setContentView(R.layout.home_layout);
    super.onCreate(savedInstanceState);
    fragmentManager = getSupportFragmentManager();
    fragment = fragmentManager.findFragmentById(R.id.layFragment);

    initialize();


@Override
public void onConfigurationChanged(Configuration newConfig) 
    setContentView(R.layout.home_layout);
    initialize();
    super.onConfigurationChanged(newConfig);


private void initialize() 
    layStatus = (LinearLayout) findViewById(R.id.layStatus);
    txtStatus = (TextView) findViewById(R.id.txtStatus);
    ....
    handleFragments(lastFragmentId);


public void handleFragments(int fragmentId) 
        if (fragment == null) 
            FragmentTransaction ft = fragmentManager.beginTransaction();
            if (fragmentId==someFragmentId)
                ft.replace(R.id.layFragment, new FragmentSomeFragment());
            
            else
            ....

            ft.commit();
        

在我的 android 清单中,活动声明为:

 <activity
        android:name=".HomeActivity"
        android:configChanges="keyboardHidden|orientation" />
<activity

在关于 SO 的另一个问题中,我发现这可能是由 Support 库中的错误引起的,我是否添加了任何运气:

// needed as a workaround for a bug in the Support library
@Override
protected void onSaveInstanceState(Bundle outState) 
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);

我的应用从 android 2.2 运行,我正在使用 android-support-v4.jar 支持库来支持片段。

日志如下:

07-27 11:56:20.399: E/AndroidRuntime(16021): FATAL EXCEPTION: main
07-27 11:56:20.399: E/AndroidRuntime(16021): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1299)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1310)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:541)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:525)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.rightcab.driver.core.HomeActivity.handleFragments(HomeActivity.java:341)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.rightcab.driver.core.HomeActivity.initialize(HomeActivity.java:128)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.rightcab.driver.core.HomeActivity.onConfigurationChanged(HomeActivity.java:153)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.app.ActivityThread.performConfigurationChanged(ActivityThread.java:3618)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.app.ActivityThread.handleActivityConfigurationChanged(ActivityThread.java:3771)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1328)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.os.Handler.dispatchMessage(Handler.java:99)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.os.Looper.loop(Looper.java:137)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at android.app.ActivityThread.main(ActivityThread.java:4745)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at java.lang.reflect.Method.invokeNative(Native Method)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at java.lang.reflect.Method.invoke(Method.java:511)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:556)
07-27 11:56:20.399: E/AndroidRuntime(16021):    at dalvik.system.NativeStart.main(Native Method)

【问题讨论】:

【参考方案1】:

首先,如我所见,您希望自己处理配置更改。为了让 API 级别 13+ 正常工作,您必须向 configChanges 参数添加一个值,如 here 所述。

接下来,当用户离开您的主要活动时,会为此调用 onSaveInstanceStateonPause 方法。当用户旋转设备并回到您的主要活动时。 onConfigurationChanged 方法被调用之前 onResume()。因此,您的活动仍处于暂停状态,您无法执行FragmentTransaction

此外,如果我们查看源代码,我们可以看到onResume 方法的以下注释:

分派 onResume() 到片段。请注意,为了更好 与旧版本平台的互操作,在点 这个调用附加到活动的片段是不是 恢复。这意味着在某些情况下,之前的状态可能仍然 被保存,不允许修改状态的片段事务。 要正确与处于正确状态的片段交互,您应该 而是覆盖 @link #onResumeFragments()。

因此,在您的 Activity 中操作片段的正确位置是覆盖 onResumeFragments 方法,正如我们可以在源代码中对该方法的注释中看到的那样:

这是面向片段的 @link #onResume() 版本 您可以覆盖以同时在 Activity 中执行操作 恢复其片段的点。一定要经常打电话 到超类。

protected void onResumeFragments() 
    super.onResumeFragments();

    // YOUR STUFF IS HERE

【讨论】:

我无法使用 onResumeFragments,因为它在我的 Activity 中不可用。也许是因为我正在针对 SDK 2.2 构建?我尝试在活动的 onResume 中添加 handleFragments() 但结果仍然相同。有什么想法吗? @Alin,如果您使用片段,您的活动应该来自FragmentActivity,并且您的项目应该引用 v4 兼容性库。 onResumeFragments 是 FragmentActivity 的受保护方法,您可以覆盖它! 你说得对,我一定是写错了,我又试了一遍,现在我有了 onResumeFragments()。我之前提出的场景现在似乎可以工作了:onResumeFragments 我调用了 handleFragments() 并且没有错误,并且片段的布局根据当前方向发生了变化。但是,当我在屏幕上使用片段进行主要活动并更改方向时,我的 handleFragmens 不会在任何地方被调用,因此我留在片段应该位于的空白位置。那么......我应该如何处理 onConfigurationChanged 中的 handleFragment ? @Alin,正如我所见,您根据当前方向替换片段。那么,自己处理方向变化的原因是什么? @Alin,是的,当您旋转位于主片段上的设备时,onPauseonResumeonPostResumeonResumeFragments 方法不会被调用,因为您说 Android:“我会自己处理配置更改!”。我建议你去掉AndroidManifest.xml中的configChanges参数,并在onCreate方法中设置新的布局。 其实我想我想通了:如果我在 onPause 中创建一个标志并且知道应用程序已暂停,那么不要在 onConfigurationChanged 中使用 handleFragments()。现在它似乎工作了,将进行一些进一步的测试【参考方案2】:

如果 StenaviN 建议 onConfigurationChange() 在 resume() 之前返回您的活动:

这是生命周期:

onCreate()
onResume()
// Move away from you're Activity
onPause()
// Move back to your Activity
onConfigurationChange()
onResume()

但重要的是:

如果您恢复Activity 或更改ActivityOrientation,您的Fragments 就可以了!您不需要用新副本替换旧副本,事实上您不应该!如果您只是删除此行,则不会有问题:

handleFragments(lastFragmentId);

然而如果你这样做是因为你需要你的Fragment 来加载一个新的布局资源 (layout/frag.xml => layout-land/frag.xml) 那么你需要做一些事情像这样:

boolean mResumed = false;
onPause() 
    mResumed = false;


onResume() 
    mResumed = true;
 

...

    if(mResumed) handleFragments(lastFragmentId);

【讨论】:

@Greame,正如我在第一个答案中提到的,当活动的 onResume 方法完成时,片段还没有恢复。而且您仍然无法执行片段事务。看看这个文件&lt;sdk&gt;\extras\android\compatibility\v4\src\java\android\support\v4\app\FragmentActivity.java。您可以在onPostResumeonResumeFragments 方法中执行片段事务,之前调用super 实现。 我不建议在任何其他生命周期状态下调用FragmentTransaction.commit(),而不是“恢复”(又名运行),我建议仅在指示 Activity 处于正确状态的标志时调用它。跨度> 我现在理解你了。但是,如果您想在每个方向更改时替换片段,那么自己处理方向有什么意义?为什么不让系统处理方向变化,去掉configChanges参数,只在onCreate中设置正确的布局和实例化正确的片段? 我同意 - 如果你想使用不同的资源,你应该尽量不要覆盖配置更改。 是的,我摆脱了对配置更改的处理,现在它工作得很好。我只需要确保重要的值在一个全局类中,这样我就可以轻松地重新创建应用程序的状态谢谢 Graeme。【参考方案3】:

您使用的是最新版本的 support-v4 库吗?它解决了我的类似问题。

【讨论】:

【参考方案4】:

如果您坚持使用 r7 版本的支持库(例如,因为您正在使用 maven 并且急切地等待更新.. ;)),那么您可以使用 onPostResume 来避免这个问题。如果你的版本是 r11 或以上,那么你可以切换到onResumeFragements

【讨论】:

以上是关于Android Fragment 和方向更改导致:IllegalStateException: Can not perform this action after onSaveInstanceStat的主要内容,如果未能解决你的问题,请参考以下文章

Android Fragment 创建了两次方向更改

保存图像后由于方向更改导致 Android 显示图像捕获错误

避免Android在方向更改时自动重新添加我的片段

片段标签的使用

Android ViewPager2 笔记

Android ViewPager2 笔记