试图在Android上以可靠的方式执行异步操作是不必要的麻烦,即AsyncTask确实在概念上存在缺陷吗?还是我只是缺少一些东西?
现在,一切都在引入片段之前。随着Fragments的引入,onRetainNonConfigurationInstance()已被弃用。因此,最新的Google宽容黑客手段是使用永久性的非UI片段,该片段会在配置发生变化(例如,旋转屏幕,更改语言设置等)时与您的活动相关联/分离。
范例:https: //code.google.com/p/android/issues/detail?id = 23096#c4
从理论上讲,上面的技巧可以使您摆脱恐惧:
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。片段本身还具有子片段管理器,该子片段管理器用于嵌套片段。不幸的是,子片段管理器根本没有与活动的片段管理器保持同步。因此,尽管活动的片段管理器是最新的,并且始终具有正确的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
无论如何,摆脱话题。
因此,您可能会认为问题的解决方案很简单, 这似乎有点像是反义词, 即向您的子片段管理器中添加另一个漂亮的非UI片段。大概它的回调与它的内部状态是同步的,一切都会变得花花公子。
又错了!
Android不支持将保留的片段实例作为子片段附加到其他片段(也称为嵌套片段)。现在,事后看来,这应该是有道理的。如果由于活动未保留而在活动被销毁时父片段被销毁,则无处可以重新附加子片段。所以这只是行不通的。
是否有人可以通过异步代码回调来确定何时可以安全地 对子片段 执行片段事务?
如果可以忍受,请使用React Native。我知道,我知道…“肮脏的网络技术”,但说真的,Android SDK简直是一场灾难,所以请放下你的骄傲,随它去吧。您可能会感到惊讶;我知道我做到了!
不用担心,我建议从根本上改变您的联网方法。触发请求并运行请求处理程序以更新UI不适用于Android的组件生命周期。
而是尝试以下方法之一:
LocalBroadcastReceiver
Intent
我相信这是Google的最新官方解决方案。但是,该解决方案确实无法很好地扩展。如果您不习惯自己弄乱队列,处理程序和保留实例状态,那么这可能是您唯一的选择……但是请不要说我没有警告您!
Android活动和片段支持可与AsyncTaskLoader一起使用的LoaderManager。在后台,装载程序管理器的保留方式与保留的片段完全相同。因此,此解决方案与下面的我自己的解决方案确实有一些共同点。AsyncTaskLoader是一种部分罐头解决方案,在技术上可以 正常工作 。但是,API非常麻烦。我相信您会在使用它的几分钟后注意到它。 __
首先,我的解决方案绝非易事。但是,一旦实现工作顺利进行,就很容易使用,并且可以根据自己的需求对其进行自定义。
我使用保留的片段,该片段被添加到“活动”的片段管理器中(或者在我的情况下支持片段管理器)。这是我的问题中提到的相同技术。该片段充当各种 提供程序 ,可跟踪其附加到的活动,并具有Message和Runnable(实际上是自定义子类)队列。当不再保存实例状态 并且 相应的处理程序(或可运行的)“准备执行” 时,将执行队列。
每个处理程序/可运行程序都存储一个引用 使用者 的UUID 。 消费者 通常是活动中某处的碎片(可以安全地嵌套)。当 使用者 片段附加到活动时,它将查找 提供者 片段并使用其UUID进行注册。
使用某种抽象(例如UUID),而不是直接引用 使用者 (即片段),这一点很重要。这是因为片段经常被破坏和重新创建,并且您希望回调具有对新片段的“引用”。不是属于被破坏活动的旧物品。因此,不幸的是,您很少可以安全地使用匿名类捕获的变量。同样,这是因为这些变量可能引用了一个已损坏的旧片段或活动。相反,你必须要求 供应商 为 消费者 处理程序已存储的UUID相匹配。然后,您可以投放此 消费者 到它实际存在的任何片段/对象并安全地使用它,因为您知道它的最新片段具有有效的Context(活动)。
当 使用者 (由UUID引用)准备就绪时,处理程序(或可运行)将“准备执行” 。除了 提供者 之外,还需要检查 使用者 是否准备就绪,因为正如我的问题中所述,即使提供者另有说明,使用者片段也可能认为其实例状态已保存。如果使用者(或提供者)尚未准备就绪,则可以将消息(或可运行)放入提供者的队列中。 __
当 使用者 片段到达onResume()时,它通知 提供者 它已准备好使用已排队的消息/可运行对象。此时, 提供者 可以尝试执行其队列中属于准备就绪的 使用者的 任何操作。
这导致处理程序始终使用有效的上下文(提供者引用的活动)和最新的有效片段(也称为“消费者”)执行。
该解决方案非常复杂,但是一旦确定了实现方法,它就可以完美地工作。如果有人提出了一个更简单的解决方案,那么我很乐意听到。