【问题标题】:IlegalStateException when onItemClick after restart重启后单击项目时出现 IllegalStateException
【发布时间】:2016-12-08 16:58:27
【问题描述】:

要重现,请获取SSCCE Android Project on Github 和:

触摸汉堡以显示导航菜单
选择员工
选择员工
触摸返回按钮
触摸概览按钮
从列表中选择应用程序
触摸汉堡显示导航菜单
选择员工
选择一个员工 => IllegalStateException

java.lang.IllegalStateException: 之后无法执行此操作 onSaveInstanceState 在 android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1538) 在 android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1556) 在 android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:696) 在 android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:662) 在 example.com.replacefragments_onitemclick.fragments.FragmentChange.onFragmentChange(FragmentChange.java:88)

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

异常的原因很清楚:使用 replace 语句,它试图替换附加到现在不存在的 Activity 实例的片段。

onSaveInstanceState() 覆盖为suggested here 无效。

许多问题建议使用commitAllowingStateLoss()。它不能解决问题,而且显然是一种黑客攻击。

此外,还有一些答案说要保留对旧 Activity 的引用。这似乎不对。

如何防止这个异常?

【问题讨论】:

    标签: android illegalstateexception onsaveinstancestate


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

      FragmentChange 中,您使用 Singleton 设计模式,在您第一次启动时,当您点击员工时,您可以通过您的FragmentActivity 已经存在,当按下时活动消失了,当再次打开应用程序时,使用 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;
      }
      

      【讨论】:

        【解决方案3】:

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

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

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

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

          • FragmentTransactiononPostExecute() 方法内提交,导致抛出异常。

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

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

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

        希望这会对您有所帮助。编码愉快!!!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多