繁体   English   中英

嵌套片段 - IllegalStateException“onSaveInstanceState后无法执行此操作”

[英]Nested Fragments - IllegalStateException “Can not perform this action after onSaveInstanceState”

背景

Android中的异步回调

尝试在Android上以可靠的方式执行异步操作是不必要的错综复杂的,即AsyncTask在概念上是否真的有缺陷,或者我只是缺少某些东西?

现在,这是在引入Fragments之前的全部内容。 随着Fragments的引入, onRetainNonConfigurationInstance()已被弃用。 因此,最新的谷歌纵容黑客是使用持久的非UI片段,当发生配置更改时(即旋转屏幕,更改语言设置等),它会从您的活动中附加/分离。

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

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

从理论上讲,上面的黑客可以让你绕过可怕的:

IllegalStateException - "Can not perform this action after onSaveInstanceState"

因为持久的非UI片段将接收onViewStateRestored()(或者onResume)和onSaveInstanceState()(或者onPause)的回调。 因此,您可以判断实例状态何时被保存/恢复。 这是一个很简单的代码,但是利用这些知识,您可以排队异步回调,直到活动的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);
    }
}

所以从理论上讲,现在你知道什么时候执行片段事务是安全的,因此你可以避免可怕的IllegalStateException。

错误!

嵌套片段

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

现在,如果您查看了支持库中的Fragment.java和FragmentManager.java,您将不会感到惊讶的是,所有与片段有关的内容都容易出错。 设计和代码质量是......啊,有问题。 你喜欢布尔吗?

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

无论如何,有点偏离主题。

死胡同解决方案

所以,你可能认为问题的解决方案很简单, 就像这一点似乎是一个反义词,为你的子片段管理器添加另一个漂亮的hacky非UI片段。 据推测,它的回调与其内部状态是同步的,事情都会花哨。

又错了!

Android不支持作为子项附加到其他片段(也称为嵌套片段)的保留片段实例。 现在,事后看来这应该是有道理的。 如果在活动被销毁时销毁了父片段,因为它未被保留,则无法重新附加子片段。 所以这不会起作用。

我的问题

是否有人有一个解决方案来确定何时可以安全地对子片段执行片段事务以及异步代码回调?

更新2

反应原生

如果你可以忍受它,使用React Native。 我知道,我知道......“肮脏的网络技术”,但严肃地说,Android SDK是一场灾难,所以吞下你的骄傲,只是试一试。 你可能会惊讶自己; 我知道我做到了!

不能或不会使用React Native

不用担心,我建议从根本上改变你的网络方法。 触发请求并运行请求处理程序以更新UI仅对Android的组件生命周期不起作用。

而是尝试以下之一:

  1. 转移到基于LocalBroadcastReceiver简单消息传递系统,并且具有长生命对象(常规Java类或Android服务)在您的应用程序的本地状态发生更改时执行请求并触发事件。 然后在您的Activity / Fragment中,只听取某些Intent并相应地更新。
  2. 使用Reactive事件库(例如RxJava)。 我自己并没有在Android上试过这个,但是使用类似的概念库ReactiveCocoa在Mac /桌面应用程序上取得了相当不错的成功。 不可否认,这些库有一个相当陡峭的学习曲线,但是一旦你习惯它,这种方法就会非常清爽。

更新1:快速和肮脏(官方)解决方案

我相信这是谷歌最新的官方解决方案。 但是,解决方案确实不能很好地扩展。 如果你不熟悉队列,处理程序和保留的实例状态,那么这可能是你唯一的选择...但不要说我没有警告你!

Android活动和片段支持可以与AsyncTaskLoader一起使用的LoaderManager 在幕后,加载程序管理器的保留方式与保留的碎片完全相同。 因此,这个解决方案确实与我自己的解决方案有一点共同之处。 AsyncTaskLoader是一种部分预先固定的解决方案,在技术上可行 但是,API非常麻烦; 因为我相信你会在几分钟内注意到它。

我的解决方案

首先,我的解决方案实施起来并不简单。 但是,一旦您的实施工作,使用起来轻而易举,您可以根据自己的内容进行自定义。

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

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

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

消费者 (由UUID引用)准备就绪时,处理程序(或可运行)将“准备好执行”。 除了提供者之外,有必要检查消费者是否已准备就绪,因为正如我的问题所述,消费者片段可能认为即使提供者另有说明,也会保存其实例状态。 如果使用者(或提供者)尚未就绪,则将Message(或runnable)放入提供者的队列中。

使用者片段到达onResume()时,它通知提供者它已准备好使用排队的消息/ runnables。 此时, 提供程序可以尝试在其刚刚准备就绪的消费者的队列中执行任何操作。

这导致处理程序总是使用有效的Context(提供者引用的Activity)和最新的有效Fragment(又名“consumer”)执行。

结论

解决方案非常复杂,但是一旦你弄清楚如何实现它,它确实可以正常工作。 如果有人想出一个更简单的解决方案,那么我很乐意听到它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM