AsyncTask是真的在概念上有缺陷还是我错过了什么?

我已经调查了几个月的这个问题,提出了不同的解决方案,我不满意,因为他们都是大规模的黑客。 我还是不敢相信一个有设计上的缺陷的人把它变成了框架,没有人在谈论这个框架,所以我想我一定是错过了一些东西。

问题是与AsyncTask 。 根据它的文件

“允许执行后台操作并在UI线程上发布结果,而无需操作线程和/或处理程序。

这个例子继续展示了如何在onPostExecute()调用一些示例showDialog()方法。 然而,这似乎完全是设计的 ,因为显示一个对话框总是需要对一个有效的Context的引用,并且一个AsyncTask 不能持有对上下文对象的强引用

原因是显而易见的:如果活动被破坏触发任务呢? 这可能会一直发生,例如因为您翻转了屏幕。 如果这个任务持有对创建它的上下文的引用,那么你不仅要坚持一个无用的上下文对象(这个窗口已经被销毁了, 任何 UI交互都将失败,并且有异常!),你甚至有可能冒险创建一个内存泄漏。

除非我的逻辑在这里是有缺陷的,这意味着: onPostExecute()是完全没有用的,因为如果你不能访问任何上下文,在UI线程上运行这个方法有什么好处呢? 这里你不能做任何有意义的事情。

一种解决方法是不传递上下文实例到AsyncTask,而是一个Handler实例。 这是有效的:由于处理程序松散地绑定上下文和任务,您可以在它们之间交换消息,而不会冒险泄漏(对吧?)。 但是这意味着AsyncTask的前提,即你不需要打扰处理程序,是错误的。 它也似乎滥用处理程序,因为你是发送和接收消息在同一个线程(你创建它的UI线程,并通过它发送onPostExecute()这也是在UI线程上执行)。

为了解决这个问题,即使采用了这种解决方法,仍然存在这样的问题,即当上下文被破坏时,您不会记录它所触发的任务。 这意味着在重新创建上下文时,例如在屏幕方向更改之后,您必须重新启动任何任务。 这是缓慢和浪费。

我的解决方案( 在Droid-Fu库中实现 )是维护WeakReference从组件名称到当前实例在唯一应用程序对象上的映射。 每当一个AsyncTask被启动时,它就会在该映射中记录调用上下文,并且在每个回调函数中,都会从该映射中获取当前上下文实例。 这可以确保您永远不会引用陈旧的上下文实例, 并且您始终可以访问回调中的有效上下文,以便您可以在那里进行有意义的UI工作。 它也不会泄漏,因为引用很弱,并且在给定组件的实例不再存在时被清除。

尽管如此,这是一个复杂的解决方法,并且需要对Droid-Fu库类进行子类化,这使得它成为一种相当干扰的方法。

现在我只想知道:我只是大量地缺少一些东西或是AsyncTask真的完全有缺陷? 你的经验如何与它合作? 你是怎么解决这些问题的?

感谢您的输入。

怎么样这样的事情:

 class MyActivity extends Activity { Worker mWorker; static class Worker extends AsyncTask<URL, Integer, Long> { MyActivity mActivity; Worker(MyActivity activity) { mActivity = activity; } @Override protected Long doInBackground(URL... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100)); } return totalSize; } @Override protected void onProgressUpdate(Integer... progress) { if (mActivity != null) { mActivity.setProgressPercent(progress[0]); } } @Override protected void onPostExecute(Long result) { if (mActivity != null) { mActivity.showDialog("Downloaded " + result + " bytes"); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mWorker = (Worker)getLastNonConfigurationInstance(); if (mWorker != null) { mWorker.mActivity = this; } ... } @Override public Object onRetainNonConfigurationInstance() { return mWorker; } @Override protected void onDestroy() { super.onDestroy(); if (mWorker != null) { mWorker.mActivity = null; } } void startWork() { mWorker = new Worker(this); mWorker.execute(...); } } 

原因是显而易见的:如果活动被破坏触发任务呢?

手动取消onDestroy() AsyncTask的活动。 手动重新将新活动关联到onCreate()AsyncTask 。 这需要一个静态内部类或一个标准的Java类,加上大概10行代码。

看起来AsyncTask不仅仅是在概念上有缺陷 。 兼容性问题也无法使用。 Android文档阅读:

首次引入时,AsyncTasks在单个后台线程上被串行执行。 从DONUT开始,将其更改为允许多个任务并行操作的线程池。 启动HONEYCOMB后,任务将重新在单个线程上执行,以避免并行执行导致的常见应用程序错误。 如果您确实需要并行执行,则可以使用 THREAD_POOL_EXECUTOR 的此方法 executeOnExecutor(Executor, Params...) 版本 ; 然而,请参阅评论那里使用的警告。

executeOnExecutor()THREAD_POOL_EXECUTOR都是在API级别11 (Android 3.0.x,HONEYCOMB)中添加的。

这意味着如果您创建两个AsyncTask来下载两个文件,第二个下载将不会启动,直到第一个下载完成。 如果您通过两台服务器聊天,并且第一台服务器关闭,则在连接到第一台服务器之前,您将不会连接到第二台服务器。 (除非您使用新的API11功能,当然,但这会使您的代码与2.x不兼容)。

如果你想要同时定位2.x和3.0+,那么这个东西变得非常棘手。

另外, 文档说:

警告:由于运行时配置更改(例如,当用户更改屏幕方向时),在使用工作线程时可能遇到的另一个问题会在您的活动中意外重启,从而可能会损坏工作线程 要了解如何在这些重新启动过程中保留任务,以及在活动被销毁时如何正确取消任务,请参阅Shelves示例应用程序的源代码。

可能我们所有人,包括谷歌,都从MVC的角度滥用AsyncTask

一个活动是一个控制器 ,并且控制器不应该开始可能超过视图的操作 。 也就是说,AsyncTasks应该从模型中使用 ,从一个没有绑定到Activity生命周期的类中记住 – 活动在循环中被销毁。 (至于视图 ,你通常不会编程从例如android.widget.Button派生的类,但你可以。通常,你唯一要做的就是使用xml。)

换句话说,将AsyncTask派生物放置在Activities的方法中是错误的。 OTOH,如果我们不能在活动中使用AsyncTasks,那么AsyncTask会失去吸引力:它曾经被广告作为一个快速简单的修复。

我不确定这是否是真的,你从一个AsyncTask的上下文引用内存泄漏的风险。

实现它们的通常方法是在Activity的一个方法的范围内创建一个新的AsyncTask实例。 所以如果这个活动被破坏了,那么一旦AsyncTask完成了,它不可能被获取,然后有资格进行垃圾回收? 所以对活动的引用将无关紧要,因为AsyncTask本身不会停留。

在您的活动中保留一个WeekReference将会更加健壮:

 public class WeakReferenceAsyncTaskTestActivity extends Activity { private static final int MAX_COUNT = 100; private ProgressBar progressBar; private AsyncTaskCounter mWorker; @SuppressWarnings("deprecation") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task_test); mWorker = (AsyncTaskCounter) getLastNonConfigurationInstance(); if (mWorker != null) { mWorker.mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(this); } progressBar = (ProgressBar) findViewById(R.id.progressBar1); progressBar.setMax(MAX_COUNT); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_async_task_test, menu); return true; } public void onStartButtonClick(View v) { startWork(); } @Override public Object onRetainNonConfigurationInstance() { return mWorker; } @Override protected void onDestroy() { super.onDestroy(); if (mWorker != null) { mWorker.mActivity = null; } } void startWork() { mWorker = new AsyncTaskCounter(this); mWorker.execute(); } static class AsyncTaskCounter extends AsyncTask<Void, Integer, Void> { WeakReference<WeakReferenceAsyncTaskTestActivity> mActivity; AsyncTaskCounter(WeakReferenceAsyncTaskTestActivity activity) { mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(activity); } private static final int SLEEP_TIME = 200; @Override protected Void doInBackground(Void... params) { for (int i = 0; i < MAX_COUNT; i++) { try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(getClass().getSimpleName(), "Progress value is " + i); Log.d(getClass().getSimpleName(), "getActivity is " + mActivity); Log.d(getClass().getSimpleName(), "this is " + this); publishProgress(i); } return null; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); if (mActivity != null) { mActivity.get().progressBar.setProgress(values[0]); } } } } 

为什么不只是在拥有的Activity中覆盖onPause()方法,并从那里取消AsyncTask呢?

你是绝对正确的 – 这就是为什么远离在活动中使用异步任务/装载器来获取数据的运动正在获得势头。 新的方法之一是使用Volley框架,一旦数据准备就绪,就会提供回调 – 与MVC模型更加一致。 Volley在2013年Google I / O大会上受到欢迎。不知道为什么更多的人不知道这一点。

我以为取消的作品,但没有。

在这里他们RTFMing:

“”如果任务已经启动,则mayInterruptIfRunning参数确定执行此任务的线程是否应该被中断以试图停止任务。“

但这并不意味着线程是可中断的。 这是一个Java的东西,而不是一个AsyncTask的东西。“

http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d

你最好把AsyncTask当作与Activity,Context,ContextWrapper等更紧密结合的东西。当它的范围被完全理解的时候,它会更加方便。

确保您的生命周期中有一个取消策略,以便它最终将被垃圾收集,不再保留对您的活动的参考,也可以进行垃圾回收。

如果你不需要取消你的AsyncTask而从Context中移走,你将会遇到内存泄漏和NullPointerException异常,如果你只需要提供Toast这样的简单对话反馈,那么你的应用程序上下文的单例将有助于避免NPE问题。

AsyncTask并不全是坏事,但是确实有很多魔法可以导致一些不可预见的陷阱。

就个人而言,我只是扩展线程,并使用回调接口来更新用户界面。 如果没有FC问题,我永远无法使AsyncTask正常工作。 我也使用非阻塞队列来管理执行池。

至于“使用它的经验”:有可能 杀死所有的AsyncTasks 的过程 ,Android将重新创建活动堆栈,以便用户不会提及任何东西。