Ben*_*ell 14 java android illegalstateexception
尝试在Android上以可靠的方式执行异步操作是不必要的错综复杂的,即AsyncTask在概念上是否真的有缺陷,或者我只是缺少某些东西?
现在,这是在引入Fragments之前的全部内容.随着Fragments的引入,onRetainNonConfigurationInstance()已被弃用.因此,最新的Google宽恕黑客是使用持久的非UI片段,当发生配置更改时(即旋转屏幕,更改语言设置等),它会从您的Activity中附加/分离.
示例:https: //code.google.com/p/android/issues/detail?id = 23096#c4
从理论上讲,上面的黑客可以让你绕过可怕的:
IllegalStateException - "Can not perform this action after onSaveInstanceState"
Run Code Online (Sandbox Code Playgroud)
因为持久的非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);
}
}
Run Code Online (Sandbox Code Playgroud)
所以从理论上讲,现在你知道什么时候执行片段事务是安全的,因此你可以避免可怕的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
Run Code Online (Sandbox Code Playgroud)
无论如何,有点偏离主题.
所以,你可能认为问题的解决方案很简单,就像这一点似乎是一个反义词,为你的子片段管理器添加另一个漂亮的hacky非UI片段.据推测,它的回调与其内部状态是同步的,事情都会花哨.
又错了!
Android不支持作为子项附加到其他片段(也称为嵌套片段)的保留片段实例.现在,事后看来这应该是有道理的.如果在活动被销毁时销毁了父片段,因为它未被保留,则无法重新附加子片段.所以这不会起作用.
是否有人有一个解决方案来确定何时可以安全地对子片段执行片段事务以及异步代码回调?
如果你能忍受,请使用 React Native。我知道,我知道……“肮脏的网络技术”,但说实话,Android SDK 是一场灾难,所以放下你的骄傲,尝试一下吧。你可能会感到惊讶;我知道我做到了!
不用担心,我建议从根本上改变你的网络方式。触发请求并运行请求处理程序来更新 UI 并不适合 Android 的组件生命周期。
请尝试以下之一:
LocalBroadcastReceiver并让长期存在的对象(常规 Java 类或 Android 服务)执行您的请求并在应用程序的本地状态更改时触发事件。然后在您的 Activity/Fragment 中,只需监听某些内容Intent并进行相应更新。我相信这是谷歌最新的官方解决方案。然而,该解决方案确实不能很好地扩展。如果您不习惯自己搞乱队列、处理程序和保留实例状态,那么这可能是您唯一的选择......但不要说我没有警告您!
Android 活动和片段支持 LoaderManager ,可以与AsyncTaskLoader一起使用。在幕后,加载管理器的保留方式与保留片段完全相同。因此,这个解决方案确实与下面我自己的解决方案有一些共同点。AsyncTaskLoader 是一个部分预装的解决方案,在技术上确实有效。然而API极其繁琐;我相信您在使用它后几分钟内就会注意到。
首先,我的解决方案实施起来并不简单。然而,一旦您的实现开始工作,使用起来就会变得轻而易举,并且您可以根据自己的喜好对其进行自定义。
我使用添加到活动的片段管理器(或者在我的情况下支持片段管理器)中的保留片段。这与我的问题中提到的技术相同。该片段充当某种提供者的角色,跟踪它所附加的活动,并具有 Message 和 Runnable (实际上是自定义子类)队列。当实例状态不再保存并且相应的处理程序(或可运行)“准备好执行”时,队列将执行。
每个处理程序/可运行程序都存储一个引用消费者的UUID 。消费者通常是活动中某处的片段(可以安全地嵌套)。当消费者片段附加到活动时,它会查找提供者片段并使用其 UUID 注册自身。
使用某种抽象(例如 UUID)非常重要,而不是直接引用消费者(即片段)。这是因为片段经常被销毁和重新创建,并且您希望回调具有对新片段的“引用”;不是属于被破坏的活动的旧的。因此,不幸的是,您很少可以安全地使用匿名类捕获的变量。同样,这是因为这些变量可能引用旧的已损坏的片段或活动。相反,您必须向提供者询问与处理程序存储的 UUID 相匹配的使用者。然后,您可以将此消费者转换为它实际上是什么片段/对象并安全地使用它,因为您知道它是具有有效上下文(活动)的最新片段。
当消费者(由 UUID 引用)准备就绪时,处理程序(或可运行的)将“准备好执行” 。除了提供者之外,还有必要检查消费者是否已准备好,因为正如我的问题中提到的,消费者片段可能认为其实例状态已保存,即使提供者另有说明。如果消费者(或提供者)还没有准备好,那么您将消息(或可运行的消息)放入提供者的队列中。
当消费者片段到达 onResume() 时,它通知提供者它已准备好消费排队的消息/可运行对象。此时,提供者可以尝试执行其队列中属于刚刚准备就绪的消费者的任何内容。
这导致处理程序始终使用有效的上下文(提供者引用的活动)和最新的有效片段(也称为“消费者”)执行。
该解决方案非常复杂,但是一旦您弄清楚如何实施它,它就会完美地工作。如果有人提出一个更简单的解决方案,那么我很高兴听到。
| 归档时间: |
|
| 查看次数: |
4847 次 |
| 最近记录: |