重启后 onItemClick 时出现 IlegalStateException

IlegalStateException when onItemClick after restart

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

触摸汉堡显示导航菜单
Select 员工
Select 一名员工
触摸后退按钮
触摸概览按钮
Select 列表中的应用程序
触摸汉堡显示导航菜单
Select 员工
Select 一个员工 => 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

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

onSaveInstanceState() 覆盖为 suggested here 无效。

许多问题建议使用 commitAllowingStateLoss()。它没有解决问题,而且显然是一种 hack。

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

如何防止这种异常?

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

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

  1. 在 Activity 生命周期方法中提交事务时要小心。 大多数应用程序只会在第一次提交事务 onCreate()被称为 and/or 以响应用户输入,因此永远不会遇到任何问题。然而,随着您的事务开始冒险进入其他 Activity 生命周期方法,例如 onActivityResult()onStart()onResume(),事情可能会变得有点棘手。例如,您不应该在 FragmentActivity#onResume() 方法内部提交事务,因为在某些情况下可以在 activity 的状态恢复之前调用该方法(请参阅 documentation for more information). If your application requires committing a transaction in an Activity life-cycle method other than onCreate(), do it in either FragmentActivity#onResumeFragments() or Activity#onPostResume(). These two methods are guaranteed to be called after the Activity has been restored to its original state, and therefore avoid the possibility of state loss all together. (As an example of how this can be done, check out this answer ).
  2. 避免在异步回调方法中执行事务。 这包括 AsyncTask#onPostExecute()LoaderManager.LoaderCallbacks#onLoadFinished() 等常用方法。在这些方法中执行事务的问题在于,在调用它们时,它们不知道 Activity 生命周期的当前状态。例如,考虑以下事件序列:

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

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

    • onPostExecute() 方法中提交了 FragmentTransaction,导致抛出异常。

    一般来说,在这些情况下避免异常的最好方法就是避免在异步回调方法中一起提交事务。 Google 工程师们似乎也同意这种看法。根据this post on the Android Developers group, the Android team considers the major shifts in UI that can result from committing FragmentTransactions from within asynchronous callback methods to be bad for the user experience. If your application requires performing the transaction inside these callback methods and there is no easy way to guarantee that the callback won’t be invoked after onSaveInstanceState(), you may have to resort to using commitAllowingStateLoss() and dealing with the state loss that might occur. (See also these two SO posts for additional hints, here and here).

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

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

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

普遍的问题是失去上下文。你是对的,保留对旧 Activity 的引用是一个坏方法,因为 Android 系统应该管理 Activity 的生命周期。但是当创建静态 FragmentChange 实例 (FragmentChange.java : 46).

时,您在 FragmentChange class 中保留了对 FragmentManager 的引用

那么真正发生的事情是:您创建了 FragmentChange 的静态实例。 FragmentChange 实例保留对与 MainActivity 实例链接的 FragmentManager 的引用。当您按下返回时,系统会调用 MainActivity 实例生命周期回调。它调用 onStop() 回调,该回调还在 FragmentManager 实例中调用 dispatchStop(),其中 mStateSaved 变量被分配给 true。然后 MainActivity 实例被销毁。但是 FragmentChange 实例被保留,因为应用程序没有被销毁,只有 activity 实例。当您 return 到应用程序时,将创建新的 MainActivity 实例。但是您的 FragmentChange 仍然保留对链接到死 MainActivity 实例的旧 FragmentManager 实例的引用。因此,没有人会调用 dispatchCreate()dispatchResume() 或任何其他方法将 mStateSaved 值恢复为 false,旧的 FragmentManager 实例未链接应用程序的生命周期。当你再次 select 雇员时,旧的 FragmentManager 实例抛出 IllegalStateException 因为 mStateSaved 仍然有真实的价值。

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

中提供了一个代码示例