-1

To reproduce, get the SSCCE Android Project on Github and :

Touch the hamburger to display navigation menu
Select Employees
Select an Employee
Touch the back button
Touch the Overview button
Select the application from the list
Touch the hamburger to display navigation menu
Select Employees
Select an Employee => IllegalStateException

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1538) at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1556) at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:696) at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:662) at example.com.replacefragments_onitemclick.fragments.FragmentChange.onFragmentChange(FragmentChange.java:88)

fragmentTransaction.commit(); // IllegalStateException:  FragmentChange.java:88

The reason for the exception is clear: With the replace statement, it is trying to replace a fragment that is attached to a now non-existent Activity instance.

Overriding onSaveInstanceState() as suggested here has no effect.

Numerous questions suggest using commitAllowingStateLoss(). It does not fix the problem and apparently is kind of a hack anyway.

Also, there are answers that say to keep a reference to the old Activity. This does not seem right.

How can I prevent this exception?

4

3 回答 3

1

FragmentTransactions当您在应用程序中使用时,请记住以下一些建议。

  1. 在 Activity 生命周期方法中提交事务时要小心。 大多数应用程序只会在第一次onCreate()被调用和/或响应用户输入时提交事务,因此永远不会遇到任何问题。但是,随着您的事务开始冒险进入其他 Activity 生命周期方法,例如onActivityResult()onStart()onResume(),事情可能会变得有些棘手。例如,您不应该在FragmentActivity#onResume()方法内提交事务,因为在某些情况下,可以在活动状态恢复之前调用方法(有关更多信息,请参阅文档)。如果您的应用程序需要在 Activity 生命周期方法中提交事务,而不是onCreate(),在FragmentActivity#onResumeFragments()Activity#onPostResume()中执行此操作。这两个方法保证在Activity恢复到原始状态后调用,一起避免了状态丢失的可能。(作为如何做到这一点的示例,请查看此答案)。
  2. 避免在异步回调方法中执行事务。这包括常用的方法,例如AsyncTask#onPostExecute()LoaderManager.LoaderCallbacks#onLoadFinished()。在这些方法中执行事务的问题在于,当它们被调用时,它们不知道 Activity 生命周期的当前状态。例如,考虑以下事件序列:

    • 一个活动执行一个AsyncTask.
    • 用户按下“Home”键,导致调用活动onSaveInstanceState()和方法。onStop()

    • AsyncTask 完成并被onPostExecute()调用,不知道 Activity 已经停止。

    • AFragmentTransaction在方法内部提交onPostExecute(),导致抛出异常。

    通常,在这些情况下避免异常的最佳方法是简单地避免在异步回调方法中一起提交事务。谷歌工程师似乎也同意这一信念。根据Android Developers group 上的这篇文章,Android 团队认为 UI 的主要变化可能是由于从FragmentTransactions异步回调方法中提交而导致的,这不利于用户体验。如果您的应用程序需要在这些回调方法中执行事务,并且没有简单的方法来保证不会在之后调用回调onSaveInstanceState(),您可能不得不求助于使用commitAllowingStateLoss()和处理可能发生的状态丢失。(另请参阅这两个 SO 帖子以获取更多提示,此处此处)。

  3. commitAllowingStateLoss()仅作为最后手段使用。commit()调用和的唯一区别commitAllowingStateLoss()是,如果发生状态丢失,后者不会抛出异常。通常你不想使用这种方法,因为它暗示有可能发生状态丢失。当然,更好的解决方案是编写应用程序,以保证在保存活动状态之前调用 commit(),因为这将带来更好的用户体验。除非无法避免状态丢失的可能性,否则commitAllowingStateLoss()不应使用。

有关 Activity 状态丢失及其对 FragmentTransaction 的影响的更多信息,请查看此链接

希望这会帮助你。快乐编码!!!

于 2016-12-12T09:17:19.097 回答
1

FragmentChange中,您使用单例设计模式,在您第一次启动时,当您单击员工时,您通过已经存在的FragmentActivity设置FragmentManager,当按下时,活动消失,当再次打开应用程序时,使用创建新活动savedInstance,但它是另一个对象。但是FragmentChange对象仍然存在使用旧的 Activity。您需要不使用 Singleton 模式,或者每次使用时更新FragmentManager 。

所以在FragmentChange你要么做

public static FragmentChange getInstance(FragmentManager fragmentManager) {
    instance = new FragmentChange(fragmentManager);
    return instance;
}

或者

public static FragmentChange getInstance(FragmentManager fragmentManager) {
    if (instance == null) {
        instance = new FragmentChange(fragmentManager);
    }
    instance.mFragmentManager = fragmentManager;
    return instance;
}
于 2016-12-11T10:57:56.493 回答
1

普遍的问题是失去上下文。您是对的,保留对旧 Activity 的引用是一种不好的方法,因为 Android 系统应该管理 Activity 的生命周期。但是您在创建静态实例时保留了对FragmentManager类中的引用。FragmentChangeFragmentChange(FragmentChange.java : 46)

那么真正发生了什么:您创建了FragmentChange. FragmentChange实例保持对FragmentManager与实例链接的引用MainActivity。当您按下返回时,系统会调用MainActivity实例生命周期回调。它调用回调,该onStop()回调也调用实例分配给变量的位置。之后实例被销毁。但是实例被保留,因为应用程序没有被破坏,只是活动实例。当您返回应用程序时,将创建新实例。但是您仍然保留对链接到死实例的旧实例的引用。所以没有人会打电话,或者dispatchStop()FragmentManagermStateSavedtrueMainActivityFragmentChangeMainActivityFragmentChangeFragmentManagerMainActivitydispatchCreate()dispatchResume()mStateSaved,或任何其他将值恢复为 的方法false,旧FragmentManager实例未链接到应用程序的生命周期。当您再次选择员工时,旧FragmentManager实例会抛出IllegalStateException,因为mStateSaved仍然具有真实值。

一般的解决方法是不创建Activity、Fragment、View、FragmentManager等的静态引用。一般来说,对于所有生命周期都是Android系统业务的类。对于您的情况,可能的解决方案之一不是保留对的引用,FragmentManager而是将其作为参数发送给 onFragmentChange。我在拉取请求中提供了一个代码示例https://github.com/emnrd-ito/ReplaceFragment-OnItemClick/pull/1/files

于 2016-12-12T09:50:46.950 回答