Android AsyncTask 用于长时间运行的操作

Posted

技术标签:

【中文标题】Android AsyncTask 用于长时间运行的操作【英文标题】:Android AsyncTask for long running operations 【发布时间】:2012-09-29 15:18:33 【问题描述】:

引用 AsyncTask 的文档找到 here,它说:

AsyncTasks 最好用于短时间的操作(最多几秒钟)。如果您需要保持线程长时间运行,强烈建议您使用 java.util.concurrent 提供的各种 API Executor、ThreadPoolExecutor、FutureTask等包。

现在我的问题出现了:为什么? doInBackground() 函数在 UI 线程之外运行,那么在这里长时间运行操作有什么危害?

【问题讨论】:

在将应用程序(使用 asyncTask)部署到实际设备时遇到的问题是,如果不使用进度条,长时间运行的 doInBackground 函数会冻结屏幕。 因为 AsyncTask 与启动它的 Activity 相关联。因此,如果 Activity 被杀死,您的 AsyncTask 实例也会被杀死。 如果我在服务中创建 AsyncTask 会怎样?这不是解决问题吗? 使用IntentService,完美解决后台长时间运行。 【参考方案1】:

这是一个非常好的问题,作为一个 android 程序员需要时间来完全理解这个问题。事实上,AsyncTask 有两个相关的主要问题:

它们与活动生命周期的联系很差 它们很容易造成内存泄漏。

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

AsyncTask 和 Activity 生命周期

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

当它完成时,AsyncTask 不会更新新 Activity 的 UI。事实上,它更新了活动的前一个实例, 不再显示。这可能导致 java.lang.IllegalArgumentException: View not attach to window manager 类型的异常,如果您 例如,使用 findViewById 来检索 Activity 中的视图。

内存泄漏问题

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

尽管如此,这意味着内部类将在其外部类实例上持有一个不可见的引用:Activity。

从长远来看,这会产生内存泄漏:如果 AsyncTask 持续很长时间,它会使活动保持“活动” 而Android想摆脱它,因为它不能再显示了。该活动不能被垃圾收集,这是一个中心 Android 在设备上保留资源的机制。


将 AsyncTasks 用于长时间运行的操作确实是一个非常非常糟糕的主意。不过,它们适用于短暂的生命周期,例如在 1 或 2 秒后更新视图。

我鼓励您下载RoboSpice Motivations app,它确实深入地解释了这一点,并提供了执行某些后台操作的不同方法的示例和演示。

【讨论】:

@Snicolas 嗨。我有一个应用程序,它可以扫描来自 NFC 标签的数据并发送到服务器。它在信号良好的区域工作正常,但没有信号使网络呼叫继续运行的 AsyncTask。例如,进度对话框会运行几分钟,然后当它消失时,屏幕会变黑且无响应。我的 AsyncTask 是一个内部类。我正在编写一个处理程序以在 X 秒后取消任务。该应用程序似乎在扫描数小时后将旧数据发送到服务器。这可能是由于 AsyncTask 没有完成,然后可能在几个小时后完成?我将不胜感激。谢谢 跟踪看看会发生什么。但是,是的,这很有可能!如果你设计好你的异步任务,你可以相当正确地取消它,如果你不想迁移到 RS 或服务,那将是一个很好的起点...... @Snicolas 感谢您的回复。我昨天在 SO 上发表了一篇文章,概述了我的问题并显示了我编写的处理程序代码,以尝试在 8 秒后停止 AsyncTask。如果你有时间,你介意看看吗?从 Handler 调用 AsyncTask.cancel(true) 会正确取消任务吗?我知道我应该定期检查我的 doInBackgroud 中 iscancelled() 的值,但我认为不适用于我的情况,因为我只是在进行单行网络调用 HttpPost 而不是在 UI 上发布更新。AsyncTask 是否有替代方法例如,是否可以从 IntentService 制作 HttPost 这里是链接***.com/questions/17725767/… 您应该尝试一下 RoboSpice(在 github 上)。 ;)【参考方案2】:

为什么?

因为AsyncTask 默认使用线程池不是你创建的。切勿占用您未创建的池中的资源,因为您不知道该池的要求是什么。如果池的文档告诉您不要占用池中的资源,则永远不要占用该池中的资源,就像这里的情况一样。

特别是,从 Android 3.2 开始,AsyncTask 默认使用的线程池(对于 android:targetSdkVersion 设置为 13 或更高的应用程序)只有 一个 线程在其中 -- 如果你无限期地占用这个线程,你的其他任务都不会运行。

【讨论】:

感谢您的解释。我找不到关于将 AsyncTasks 用于长时间运行的操作的任何真正错误(尽管我通常自己将这些委托给服务),但是您关于捆绑的论点那个 ThreadPool (你确实不能对它的大小做任何假设)似乎是正确的。作为对 Anup 关于使用服务的帖子的补充:该服务本身也应该在后台线程上运行任务,而不是阻塞自己的主线程。一个选项可以是 IntentService,或者,对于更复杂的并发要求,应用您自己的多线程策略。 即使我在Service中启动AsyncTask也一样吗?我的意思是,长期运行还会有问题吗? @eddy:是的,因为这不会改变线程池的性质。对于Service,只需使用ThreadThreadPoolExecutor 谢谢@CommonsWare 只是最后一个问题,TimerTasks 是否与 AsyncTasks 有相同的缺点?还是完全不同? @eddy: TimerTask 来自标准 Java,而不是 Android。 TimerTask 已在很大程度上被 ScheduledExecutorService 所取代(尽管它的名字是标准 Java 的一部分)。两者都与 Android 无关,因此如果您希望这些东西在后台运行,您仍然需要服务。而且,您真的应该考虑来自 Android 的AlarmManager,这样您就不需要只看时钟滴答的服务。【参考方案3】:

Aysnc 任务是专门的线程,它们仍然适用于您的应用程序 GUI,但同时保持 UI 线程的资源繁重任务。因此,当更新列表、更改视图等需要您执行一些获取操作或更新操作时,您应该使用异步任务,以便您可以将这些操作保持在 UI 线程之外,但请注意这些操作仍以某种方式连接到 UI .

对于不需要更新 UI 的长时间运行的任务,您可以改用服务,因为即使没有 UI,它们也可以生存。

因此,对于短任务,请使用异步任务,因为它们可能会在您的生成活动终止后被操作系统杀死(通常不会在操作过程中终止,但会完成其任务)。对于冗长且重复的任务,请改用服务。

有关更多信息,请参阅主题:

AsyncTask for longer than a few seconds?

AsyncTask won't stop even when the activity has destroyed

【讨论】:

【参考方案4】:

AsyncTask 的问题在于,如果它被定义为 Activity 的非静态内部类,它就会有对 Activity 的引用。在异步任务容器中的activity结束,但AsyncTask中的后台工作仍在继续的情况下,activity对象不会被垃圾回收,因为有对它的引用,这会导致内存泄漏。

解决此问题的解决方案是定义活动的async task as static inner class 并使用对上下文的弱引用。

但是,将它用于简单快速的后台任务仍然是一个好主意。要使用干净的代码开发应用程序,最好使用RxJava 运行复杂的后台任务并使用结果更新 UI。

【讨论】:

以上是关于Android AsyncTask 用于长时间运行的操作的主要内容,如果未能解决你的问题,请参考以下文章

执行后台任务——AsyncTask 的替代方案?

AsyncTask 与任务

Android中使用AsyncTask实现文件下载以及进度更新提示

android中AsyncTask中的ProgressDialog

如何创建在后台运行的Android服务

Android异步服务调用策略