Android异步任务用于长时间运行的操作

92
引用在这里找到的AsyncTask文档,它说:
“AsyncTasks应该理想地用于短操作(最多几秒钟)。如果您需要让线程长时间运行,则强烈建议使用java.util.concurrent pacakge提供的各种API,如Executor、ThreadPoolExecutor和FutureTask。”
现在我的问题是:为什么?doInBackground()函数在UI线程之外运行,那么在这里有长时间运行的操作会带来什么危害呢?

2
我在将一个使用asyncTask的应用程序部署到实际设备时遇到的问题是,如果不使用进度条,长时间运行的“doInBackground”函数会冻结屏幕。 - venkatKA
2
因为AsyncTask与其启动的Activity绑定在一起。因此,如果Activity被销毁,您的AsyncTask实例也可能会被销毁。 - IgorGanapolsky
如果我在服务中创建AsyncTask,那么这样做不就解决了问题吗? - eddy
1
使用IntentService,是在后台运行长时间操作的完美解决方案。 - Keyur Thumar
4个回答

121

这是一个非常好的问题,作为一名Android程序员,完全理解这个问题需要时间。确实AsyncTask有两个主要问题:

  • 它们与活动生命周期的联系较弱
  • 它们很容易创建内存泄漏。

RoboSpice Motivations应用程序(可在Google Play上获取)中,我们详细回答了这个问题。它将深入了解AsyncTasks、Loaders、它们的特点和缺点,并向您介绍网络请求的另一种替代方案:RoboSpice。 网络请求是Android中常见的需求,本质上是长时间运行的操作。 以下是该应用程序的摘录:

AsyncTask和Activity生命周期

AsyncTasks不遵循Activity实例的生命周期。如果您在Activity中启动AsyncTask并旋转设备,则Activity将被销毁并创建一个新实例。但是AsyncTask不会死亡。它将继续存在直到完成。

而且,当它完成时,AsyncTask不会更新新Activity的UI。确实,它会更新以前的Activity实例,即不再显示的实例。如果您使用findViewById在Activity中检索视图,这可能会导致java.lang.IllegalArgumentException: View not attached to window manager类型的异常。

内存泄漏问题

在您的活动内创建AsyncTasks作为内部类非常方便。由于当任务完成或正在进行时,AsyncTask将需要操作活动的视图,因此使用活动的内部类似乎很方便:内部类可以直接访问外部类的任何字段。

然而,这意味着内部类将保留对其外部类实例(即活动)的隐藏引用。

从长远来看,这会产生内存泄漏:如果AsyncTask持续时间很长,它将保持活动处于“活动”状态与之相反,Android想要摆脱它,因为它无法再显示。该活动不能被垃圾回收,这是Android保留设备资源的核心机制。


使用AsyncTasks进行长时间运行的操作确实是个非常糟糕的主意。然而,对于像更新视图一样的短暂操作,它们还是可以的。

我建议您下载RoboSpice Motivations应用程序,它真正深入地解释了这一点,并提供了不同的方法来执行某些后台操作的示例和演示。


@Snicolas 你好。我有一个应用程序,它可以扫描NFC标签上的数据并发送到服务器。在信号良好的区域,它运行得很好,但是在没有信号的地方,执行网络调用的AsyncTask会一直运行。例如,进度对话框会运行几分钟,然后当它消失时,屏幕变黑且无响应。我的AsyncTask是一个内部类。我正在编写一个处理程序,在X秒后取消任务。应用程序似乎会在扫描后几个小时向服务器发送旧数据。这可能是由于AsyncTask没有完成,然后在几个小时后才完成吗?我将非常感激任何见解。谢谢 - turtleboy
跟踪以查看发生了什么。是的,这很可能!如果您适当地设计了异步任务,就可以相当正确地取消它,如果您不想迁移到RS或服务,那将是一个好的起点... - Snicolas
@Snicolas 感谢您的回复。昨天我在SO上发布了一篇文章,概述了我的问题并展示了我编写的处理程序代码,以尝试在8秒后停止AsyncTask。如果您有时间,能否帮忙看一下?从Handler中调用AsyncTask.cancel(true)会正确取消任务吗?我知道我应该定期检查doInBackground中iscancelled()的值,但我认为这不适用于我的情况,因为我只是进行一行webcall HttpPost而不是在UI上发布更新。除了AsyncTask之外,是否有其他选择,例如从IntentService中进行HttPost? - turtleboy
即使我在服务(Service)中启动AsyncTask,是否仍然相同?我的意思是,长时间运行的操作仍然会成为一个问题吗? - eddy
@eddy,你仍然需要处理服务生命周期。此外,设置回调不会很容易。无论如何,这有点像使用RS的降级版本,而它还有更多功能可供使用。 - Snicolas
显示剩余7条评论

38

为什么?

因为默认情况下,AsyncTask 使用一个你没有创建的线程池。不要占用你没有创建的线程池的资源,因为你不知道该线程池的要求是什么。如果该线程池的文档告诉你不要这样做,也不要占用你没有创建的线程池的资源,就像这里一样。

特别是从 Android 3.2 开始,默认情况下由 AsyncTask 使用的线程池(对于目标 SDK 版本设置为 13 或更高版本的应用程序)只有一个线程--如果你无限期地占用这个线程,那么你的其他任务将无法运行。


如果我在服务内部启动AsyncTask,它是否仍然相同?我的意思是,长时间运行的操作仍然会成为问题吗? - eddy
1
@eddy:是的,因为这不会改变线程池的性质。对于一个Service,只需使用一个Thread或者一个ThreadPoolExecutor - CommonsWare
谢谢@CommonsWare,最后一个问题,TimerTasks是否有AsyncTasks的相同缺点?还是说它完全不同? - eddy
1
@eddy:TimerTask是标准Java中的类,而不是Android中的。TimerTask已经被大量弃用,现在更推荐使用ScheduledExecutorService(尽管它的名字中带有"Executor",但它也是标准Java的一部分)。这两个类都与Android无关,所以如果你希望这些任务在后台运行,仍然需要一个服务。而且,你真的应该考虑使用Android中的AlarmManager,这样就不需要一个一直挂着的服务来监视时钟滴答声了。 - CommonsWare
明白了!非常感谢@CommonsWare。 - eddy
显示剩余2条评论

4
异步任务是专门的线程,仍然应该与您的应用程序GUI一起使用,但同时保持UI线程的资源密集型任务。因此,当更新列表、更改视图等操作需要执行某些获取或更新操作时,应该使用异步任务,以便将这些操作从UI线程中移除,但请注意这些操作仍与UI相关联。
对于不需要UI更新的长时间运行的任务,可以使用服务,因为它们甚至可以在没有UI的情况下存在。
因此,对于短任务,请使用异步任务,因为它们可以在生成活动死亡后被操作系统终止(通常不会在操作中途死亡,而是完成其任务)。对于长时间和重复性任务,请改用服务。
有关更多信息,请参见线程: 长时间运行的异步任务?即使活动已销毁,异步任务也不会停止

1
AsyncTask的问题在于,如果它被定义为活动的非静态内部类,则会引用该活动。在活动作为异步任务的容器完成的情况下,但是异步任务中的后台工作仍在继续,由于存在对其的引用,活动对象将不会被垃圾回收,这会导致内存泄漏。

解决此问题的方法是将async task定义为活动的静态内部类并使用弱引用到上下文。

但是,对于简单快速的后台任务,仍然可以使用它。为了开发具有清晰代码的应用程序,最好使用RxJava来运行复杂的后台任务并使用其结果更新UI。


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