java - 嵌套 fragment - IllegalStateException "Can not perform this action after onSaveInstanceState"

标签 java android illegalstateexception

背景

Android 中的异步回调

尝试在 Android 上以可靠的方式执行异步操作是不必要的复杂,即 Is AsyncTask really conceptually flawed or am I just missing something?

现在,这一切都在引入 Fragments 之前。随着 Fragments 的引入,onRetainNonConfigurationInstance()已被弃用。因此,最新的 Google 纵容 hack 是使用持久的非 UI fragment ,当发生配置更改(即旋转屏幕、更改语言设置等)时,该 fragment 从您的 Activity 附加/分离。

例子:
https://code.google.com/p/android/issues/detail?id=23096#c4

IllegalStateException - 在 onSaveInstanceState 之后无法执行此操作

理论上,上面的 hack 可以让你绕过可怕的:

IllegalStateException - "Can not perform this action after onSaveInstanceState"

因为持久的非 UI fragment 将接收 onViewStateRestored()(或者 onResume)和 onSaveInstanceState()(或者 onPause)的回调。因此,您可以判断何时保存/恢复实例状态。对于如此简单的事情来说,这是一段相当长的代码,但是利用这些知识,您可以将异步回调排队,直到 Activity 的 FragmentManager 在执行它们之前将其 mStateSaved 变量设置为 false。

mStateSaved 是最终负责触发此异常的变量。
private void checkStateLoss() {
    if (mStateSaved) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
    if (mNoTransactionsBecause != null) {
        throw new IllegalStateException(
                "Can not perform this action inside of " + mNoTransactionsBecause);
    }
}

所以理论上,现在您知道何时执行 fragment 事务是安全的,因此您可以避免可怕的 IllegalStateException。

错误的!

嵌套 fragment

上述解决方案仅适用于 Activity 的 FragmentManager。 fragment 本身也有一个子 fragment 管理器,用于嵌套 fragment 。不幸的是,子 fragment 管理器根本没有与 Activity 的 fragment 管理器保持同步。因此,虽然 Activity 的 fragment 管理器是最新的并且始终具有正确的 mStateSaved;子 fragment 不这么认为,并且会在不适当的时候愉快地抛出可怕的 IllegalStateException。

现在,如果您已经查看了支持库中的 Fragment.java 和 FragmentManager.java,您就不会以任何方式对与 fragment 有关的所有事情都容易出错感到惊讶。设计和代码质量是……啊,值得怀疑。你喜欢 boolean 人吗?
mHasMenu = false
mHidden = false
mInLayout = false
mIndex = 1
mFromLayout = false
mFragmentId = 0
mLoadersStarted = true
mMenuVisible = true
mNextAnim = 0
mDetached = false
mRemoving = false
mRestored = false
mResumed = true
mRetainInstance = true
mRetaining = false
mDeferStart = false
mContainerId = 0
mState = 5
mStateAfterAnimating = 0
mCheckedForLoaderManager = true
mCalled = true
mTargetIndex = -1
mTargetRequestCode = 0
mUserVisibleHint = true
mBackStackNesting = 0
mAdded = true

无论如何,有点跑题了。

死胡同解决方案

因此,您可能认为问题的解决方案很简单,这在这一点上似乎有点反义词,将另一个那些漂亮的非 UI fragment 添加到您的子 fragment 管理器中。大概它的回调与其内部状态同步,一切都会变得很花哨。

又错了!

Android 不支持作为子 fragment 附加到其他 fragment (也称为嵌套 fragment )的保留 fragment 实例。现在,事后看来,这应该是有道理的。如果父 fragment 在 Activity 被销毁时被销毁,因为它没有保留,则无处重新附加子 fragment 。所以这行不通。

我的问题

是否有人有解决方案来确定何时可以安全地结合异步代码回调对子 fragment 执行 fragment 事务?

最佳答案

更新 2

react native

如果您能忍受,请使用 React Native。我知道,我知道......“肮脏的网络技术”,但说真的,Android SDK 是一场灾难,所以放下你的骄傲,试一试吧。你可能会让自己大吃一惊;我知道我做到了!

不能或不会使用 React Native

不用担心,我建议从根本上改变您的网络方法。触发请求并运行请求处理程序来更新 UI 并不能很好地与 Android 的组件生命周期配合使用。

而是尝试以下之一:

  • 转向基于 LocalBroadcastReceiver 的简单消息传递系统并且当您的应用程序的本地状态发生变化时,让长期存在的对象(常规 Java 类或 Android 服务)执行您的请求并触发事件。然后在您的 Activity/Fragment 中,只听确定 Intent并相应更新。
  • 使用响应式(Reactive)事件库(例如 RxJava)。我自己没有在 Android 上尝试过这个,但是使用类似的概念库 ReactiveCocoa for a Mac/desktop app 取得了相当不错的成功。诚然,这些库有一个相当陡峭的学习曲线,但是一旦你习惯了这种方法就会让人耳目一新。

  • 更新 1:Quick and Dirty(官方)解决方案

    我相信这是谷歌最新的官方解决方案。但是,该解决方案确实不能很好地扩展。如果您不习惯自己弄乱队列、处理程序和保留的实例状态,那么这可能是您唯一的选择……但不要说我没有警告过您!

    Android Activity 和 Fragment 支持 LoaderManager可与 AsyncTaskLoader 一起使用.在幕后,加载器管理器的保留方式与保留的 fragment 完全相同。因此,这个解决方案与我下面的解决方案有一些共同点。 AsyncTaskLoader 是一种在技术上可行的部分预装解决方案。但是,API 极其繁琐;因为我相信您会在使用它的几分钟内注意到。

    我的解决方案

    首先,我的解决方案并不容易实现。但是,一旦您的实现开始工作,使用起来就变得轻而易举,您可以根据自己的喜好对其进行自定义。

    我使用添加到 Activity 的 fragment 管理器(或在我的情况下支持 fragment 管理器)中的保留 fragment 。这与我的问题中提到的技术相同。这个 fragment 充当各种类型的提供者,跟踪它附加到哪个 Activity ,并具有 Message 和 Runnable(实际上是一个自定义子类)队列。当实例状态不再保存并且相应的处理程序(或可运行)“准备好执行”时,队列将执行。

    每个处理程序/可运行程序存储一个 UUID,该 UUID 引用一个使用者。消费者通常是 Activity 中某处的 fragment (可以安全地嵌套)。当消费者 fragment 附加到 Activity 时,它会查找提供者 fragment 并使用其 UUID 注册自己。

    使用某种抽象(例如 UUID)而不是直接引用使用者(即 fragment )很重要。这是因为 fragment 经常被销毁和重新创建,并且您希望回调具有对新 fragment 的“引用”;不是属于已破坏 Activity 的旧 Activity 。因此,不幸的是,您很少可以安全地使用匿名类捕获的变量。同样,这是因为这些变量可能指的是旧的销毁 fragment 或 Activity 。相反,您必须向提供者询问与处理程序存储的 UUID 相匹配的使用者。然后,您可以将此消费者转换为它实际是的任何 fragment/对象并安全地使用它,因为您知道它是具有有效上下文( Activity )的最新 fragment 。

    当消费者(由 UUID 指代)准备好时,处理程序(或可运行的)将“准备好执行”。除了提供者之外,还有必要检查消费者是否准备好,因为正如我的问题中提到的,即使提供者另有说明,消费者 fragment 也可能认为其实例状态已保存。如果使用者(或提供者)未准备好,则将消息(或可运行)放入提供者的队列中。

    当消费者 fragment 到达 onResume() 时,它会通知提供者它已准备好使用排队的消息/可运行对象。此时,提供者可以尝试执行其队列中属于刚刚准备就绪的使用者的任何内容。

    这导致处理程序始终使用有效的上下文(提供者引用的 Activity )和最新的有效 fragment (又名“消费者”)执行。

    结论

    该解决方案非常复杂,但是一旦您弄清楚如何实现它,它就可以完美地工作。如果有人提出了一个更简单的解决方案,那么我很高兴听到它。

    关于java - 嵌套 fragment - IllegalStateException "Can not perform this action after onSaveInstanceState",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17847775/

    相关文章:

    java - hibernate 刷新方法

    Android 小部件,绘制 UI 线程

    Android Kotlin : java. lang.IllegalStateException

    android - 在动画后设置 LinearLayout 的可见性

    java - 我正在尝试编写一个包含 2 个赛车手的程序,但它一直给我一个 IllegalThreadStateException

    android - fragment IllegalStateException - commit() 导致状态丢失

    java - 使用 php 或 java 打开 Edge 数据库连接

    java - 为什么使用 containerGroup 会阻止我的其他监听器工作?

    Java:选择排序我的实现与另一个

    android - 当我启动应用程序时,NetworkCallback 中的 onLost 不起作用