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

问题出在AsyncTask上。根据文档它

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

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

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

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

One workaround would be to not pass context instances to an AsyncTask, but a Handler instance. That works: since a Handler loosely binds the context and the task, you can exchange messages between them without risking a leak (right?). But that would mean that the premise of AsyncTask, namely that you don't need to bother with handlers, is wrong. It also seems like abusing Handler, since you are sending and receiving messages on the same thread (you create it on the UI thread and send through it in onPostExecute() which is also executed on the UI thread).

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

My solution to this (as implemented in the Droid-Fu library) is to maintain a mapping of WeakReferences from component names to their current instances on the unique application object. Whenever an AsyncTask is started, it records the calling context in that map, and on every callback, it will fetch the current context instance from that mapping. This ensures that you will never reference a stale context instance and you always have access to a valid context in the callbacks so you can do meaningful UI work there. It also doesn't leak, because the references are weak and are cleared when no instance of a given component exists anymore.

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

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

谢谢你的建议。


当前回答

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

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

其他回答

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

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

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

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

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

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

这样怎么样:

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

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