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

问题出在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真的完全有缺陷?你的工作经历如何?你是如何解决这些问题的?

谢谢你的建议。


当前回答

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

其他回答

这样怎么样:

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行代码。

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

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

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

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

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

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

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

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