是AsyncTask真的概念上有缺陷或我只是错过了一些东西?

我已经调查了这个问题几个月了,提出了不同的解决方案,我不满意,因为它们都是巨大的黑客。我仍然无法相信一个在设计上有缺陷的类被纳入框架,而没有人谈论它,所以我想我一定是错过了什么。

问题出在AsyncTask。根据文档它

"允许执行后台 操作并在 UI线程,而不需要操作 线程和/或处理程序。" < / p >

然后,该示例继续展示如何在onPostExecute()中调用一些示例性的showDialog()方法。然而,这对我来说似乎是完全的,因为显示一个对话框总是需要一个有效的Context引用,以及一个AsyncTask 一定不能持有对上下文对象的强引用吗

原因很明显:如果触发任务的活动被破坏了怎么办?这种情况经常发生,比如你翻动了屏幕。如果任务保存了对创建它的上下文的引用,那么你不仅保留了一个无用的上下文对象(窗口将被销毁,任何 UI交互将失败并出现异常!),你甚至有可能创建内存泄漏。

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

一个解决方法是不将上下文实例传递给AsyncTask,而是传递给Handler实例。这是可行的:因为Handler松散地绑定了上下文和任务,所以您可以在它们之间交换消息,而不会有泄漏的风险(对吧?)但这意味着AsyncTask的前提,即您不需要费心处理程序,是错误的。这看起来也像滥用Handler,因为你在同一个线程上发送和接收消息(你在UI线程上创建它,并在onPostExecute()中发送,这也在UI线程上执行)。

最重要的是,即使有了这个变通方法,你仍然有一个问题,当上下文被销毁时,你有它所触发的任务的没有记录。这意味着在重新创建上下文时,你必须重新启动任何任务,例如在屏幕方向改变之后。这既缓慢又浪费。

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

不过,这是一种复杂的解决方法,需要子类化一些Droid-Fu库类,这是一种相当具有侵入性的方法。

现在我只想知道:我只是大量丢失的东西或AsyncTask真的完全有缺陷?你的工作经历如何?你是如何解决这些问题的?

谢谢你的建议。

34046 次浏览

我不确定从AsyncTask引用上下文是否存在内存泄漏的风险。

实现它们的通常方法是在Activity的一个方法的范围内创建一个新的AsyncTask实例。所以如果活动被销毁,那么一旦AsyncTask完成,它不会是不可达的,然后有资格进行垃圾收集吗?因此对活动的引用并不重要,因为AsyncTask本身不会挂在那里。

就我个人而言,我只是扩展Thread并使用回调接口来更新UI。我永远不能让AsyncTask工作正确没有FC问题。我还使用非阻塞队列来管理执行池。

原因很明显:如果 活动被破坏 触发任务?< / p >

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

这样怎么样:

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(...);
}
}

我以为取消有用,其实没用。

他们在这里rtming:

""如果任务已经开始,则mayinterruptifrunrunning . 参数确定执行此任务的线程是否应该是 试图停止任务时被打断。”< / p > 但是,这并不意味着线程是可中断的。这是一个 Java的东西,不是AsyncTask的。" < / p >

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

为什么不重写自己活动中的onPause()方法并从那里取消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]);
}
}
}


}

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

看起来AsyncTask有点像更多的而不仅仅是在概念上有缺陷。由于兼容性问题,它也无法使用。安卓文档中写道:

__abc2 __abc3 __abc4 __abc5 __abc0 __abc6 __abc1;但是,请参阅注释中关于其使用的警告。

executeOnExecutor()THREAD_POOL_EXECUTOR都是在API级别11中添加 (Android 3.0。x,蜂窝)。

这意味着如果你创建了两个__abc0来下载两个文件,第二次下载将在第一次下载完成后才开始。如果您通过两个服务器聊天,并且第一个服务器宕机,那么在第一个服务器连接超时之前,您将不会连接到第二个服务器。(当然,除非您使用新的API11特性,但这会使您的代码与2.x不兼容)。

如果你想同时瞄准2。X和3.0+,这些东西变得非常棘手。

此外,文档表示:

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

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

活动是控制器,控制器不应该启动可能比视图活得更久的操作。也就是说,AsyncTasks应该从模型中使用,从一个不绑定到Activity生命周期的类中使用——记住,Activity在旋转时被销毁。(对于视图,你通常不编写从android.widget派生的类。纽扣,但你可以。通常,你对视图所做的唯一一件事就是xml。)

换句话说,将AsyncTask衍生物放在Activities的方法中是错误的。OTOH,如果我们不能在活动中使用AsyncTask, AsyncTask就失去了它的吸引力:它曾经被宣传为一个快速和简单的修复。

你是绝对正确的-这就是为什么在活动中使用异步任务/加载器来获取数据的运动正在获得动力。其中一种新方法是使用凌空抽射框架,它本质上是在数据准备好后提供一个回调-与MVC模型更加一致。凌空在谷歌I/O 2013普及。不知道为什么更多的人没有意识到这一点。

你最好把AsyncTask看作是与Activity、Context、ContextWrapper等紧密耦合的东西。当它的范围被充分理解时,它更方便。

确保您在生命周期中有一个取消策略,这样它最终将被垃圾收集,不再保留对您的活动的引用,它也可以被垃圾收集。

如果不取消AsyncTask,而遍历离开你的上下文,你会遇到内存泄漏和nullpointerexception,如果你只是需要提供一个简单的对话框吐司反馈,那么你的应用上下文的单例将有助于避免NPE问题。

AsyncTask并不全是坏的,但肯定有很多神奇的东西会导致一些不可预见的陷阱。