重启后onItemClick时出现IlegalStateException

时间:2016-12-08 16:58:28

标签: android illegalstateexception onsaveinstancestate

要重现,请获取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)       at example.com.replacefragments_onitemclick.fragments.FragmentChange.onFragmentChange(FragmentChange.java:88)

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

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

onSaveInstanceState()覆盖为suggested here无效。

许多问题建议使用commitAllowingStateLoss()。它无法解决问题,显然无论如何都是一种黑客攻击。

此外,还有一些答案可以保留对旧活动的引用。这似乎不对。

如何防止此异常?

3 个答案:

答案 0 :(得分:1)

FragmentChange 中,您使用 Singleton 设计模式,在您第一次启动时,当您点击一名员工时,您通过您的 FragmentManager 进行设置已经存在的 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;
}

答案 1 :(得分:1)

在您的应用程序中使用FragmentTransactions时,以下是一些建议。[/ p>

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

    • 活动会执行AsyncTask
    • 用户按下“主页”键,调用活动的onSaveInstanceState()onStop()方法。

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

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

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

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

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

    希望这会对你有所帮助。快乐的编码!!!

答案 2 :(得分:1)

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

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

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

中提供了一个代码示例