AsyncTask真的在概念上存在缺陷吗,还是我漏掉了什么?

265
我已经调查了这个问题数月,提出了不同的解决方案,但都不满意,因为它们都是巨大的hack。我仍然无法相信在框架中存在这样一个设计缺陷的类,并且没有人谈论它,所以我想我一定是漏掉了什么。
问题出在AsyncTask上。根据文档,它允许在不必操作线程和/或处理程序的情况下,在后台执行操作并在UI线程上发布结果。例子继续展示了如何在onPostExecute()中调用一些showDialog()方法,然而,这对我来说似乎完全是牵强附会的,因为显示对话框总是需要引用有效的上下文,而AsyncTask绝不能保留对上下文对象的强引用。
原因很明显:如果触发任务的活动被销毁怎么办?这可能随时发生,例如,因为您翻转了屏幕。如果任务保留了创建它的上下文的引用,那么你不仅保存了一个无用的上下文对象(窗口将已被销毁,并且任何UI交互都会失败并抛出异常!),而且还有可能造成内存泄漏。
除非我的逻辑出现错误,否则这意味着:onPostExecute()是完全无用的,因为如果您没有访问任何上下文,这个方法在UI线程上运行有什么好处呢?你在这里无法做任何有意义的事情。
一种解决方法是不向AsyncTask传递上下文实例,而是传递Handler实例。这样可以避免泄漏的风险,因为Handler松散地绑定了上下文和任务,你可以在它们之间交换消息(对吗?)。但这意味着AsyncTask的前提是错误的,即你不需要烦恼处理程序。同时,这似乎滥用了Handler,因为你在同一个线程上发送和接收消息(你在UI线程上创建它,并在onPostExecute()中通过它发送)。
更糟糕的是,即使使用这种解决方法,当上下文被销毁时,你仍然无法记录所触发的任务。这意味着在重新创建上下文后(例如,在屏幕方向改变后),你必须重新启动所有的任务。这很慢也很浪费。
我的解决方案(在Droid-Fu库中实现)是在唯一的应用程序对象上维护从组件名称到它们当前实例的WeakReference映射。每当启动一个AsyncTask时,它就会在该映射中记录调用上下文,并在每个回调时从该映射中获取当前上下文实例。这确保你永远不会引用过期的上下文实例,同时在回调中始终有一个有效的上下文,因此你可以在那里进行有意义的UI工作。这也不会泄漏,因为引用是弱引用,并且在给定组件的任何实例不存在时被清除。
尽管如此,这仍然是一种复杂的解决方法,并需要对Droid-Fu库中的某些类进行子类化,使其成为相当侵入性的方法。

现在我只是想知道:我是不是漏掉了什么重要的东西,还是AsyncTask真的存在很大的缺陷?你们使用它的经验如何?你们是如何解决这些问题的?

谢谢你的回答。


2
如果您感到好奇,我们最近在点火核心库中添加了一个名为IgnitedAsyncTask的类,它通过使用Dianne下面概述的连接/断开模式,在所有回调中添加了对类型安全上下文访问的支持。它还允许抛出异常并在单独的回调中处理它们。请参见https://github.com/kaeppler/ignition-core/blob/master/src/com/github/ignition/core/tasks/IgnitedAsyncTask.java。 - mxk
请查看此链接:https://gist.github.com/1393552 - mxk
1
这个问题也与此问题有关。 - Alex Lockwood
我将异步任务添加到一个ArrayList中,并确保在某个特定点关闭它们。 - NightSkyCode
12个回答

0

就我个人而言,我喜欢扩展Thread并使用回调接口来更新UI。我无法在不出现FC问题的情况下让AsyncTask正常工作。我还使用非阻塞队列来管理执行池。


1
你的强制关闭很可能是因为我提到的问题:你尝试引用一个已经超出作用域的上下文(即其窗口已被销毁),这将导致框架异常。 - mxk
不...实际上是因为AsyncTask内置的队列很糟糕。我总是使用getApplicationContext()。如果只有几个操作,我对AsyncTask没有问题...但我正在编写一个媒体播放器,在后台更新专辑封面...在我的测试中,我有120个没有封面的专辑...所以,虽然我的应用程序没有完全关闭,但asynctask仍然会抛出错误...所以我建立了一个单例类,带有一个管理进程的队列,到目前为止它运行得很好。 - androidworkz

-1

关于"使用它的经验": 可能可以同时杀掉进程和所有AsyncTasks, Android会重新创建活动堆栈,这样用户就不会注意到任何事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接