小编典典

AsyncTask 真的在概念上存在缺陷还是我只是遗漏了什么?

all

我已经调查这个问题几个月了,想出了不同的解决方案,我不满意,因为它们都是大规模的黑客攻击。我仍然无法相信一个在设计上有缺陷的类进入了框架并且没有人在谈论它,所以我想我一定是遗漏了一些东西。

问题出在AsyncTask. 根据文件它

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

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

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

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

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

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

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

尽管如此,这是一个复杂的解决方法,并且需要对一些 Droid-Fu 库类进行子类化,这使得这是一种非常侵入性的方法。

现在我只想知道: 我只是大量遗漏了某些东西还是 AsyncTask 真的完全有缺陷?您使用它的经验如何?你是如何解决这些问题的?

感谢您的输入。


阅读 28

收藏
2022-05-13

共1个答案

小编典典

像这样的东西怎么样:

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