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

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

谢谢你的建议。


当前回答

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

其他回答

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

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

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

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

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

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