问题描述
我已经调查这个问题几个月了,想出了不同的解决方案,我不满意,因为它们都是大规模的黑客攻击.我仍然无法相信一个在设计上有缺陷的类将其纳入框架而没有人在谈论它,所以我想我一定是遗漏了一些东西.
I have investigated this problem for months now, came up with different solutions to it, which I am not happy with since they are all massive hacks. I still cannot believe that a class that flawed in design made it into the framework and no-one is talking about it, so I guess I just must be missing something.
问题在于AsyncTask
.根据文档它
"允许执行背景操作并发布结果无需操作的 UI 线程线程和/或处理程序."
然后该示例继续展示如何在 onPostExecute()
中调用一些示例性的 showDialog()
方法.然而,这对我来说似乎完全是人为的,因为显示一个对话框总是需要引用一个有效的Context
,并且一个 AsyncTask绝不能持有对一个上下文对象.
The example then continues to show how some exemplary showDialog()
method is called in onPostExecute()
. This, however, seems entirely contrived to me, because showing a dialog always needs a reference to a valid Context
, and an AsyncTask must never hold a strong reference to a context object.
原因很明显:如果触发任务的活动被销毁怎么办?这可能一直发生,例如因为你翻转了屏幕.如果任务持有对创建它的上下文的引用,那么您不仅持有一个无用的上下文对象(窗口将被销毁,任何 UI 交互都将失败并出现异常!),你甚至有可能造成内存泄漏.
The reason is obvious: what if the activity gets destroyed which triggered the task? This can happen all the time, e.g. because you flipped the screen. If the task would hold a reference to the context that created it, you're not only holding on to a useless context object (the window will have been destroyed and any UI interaction will fail with an exception!), you even risk creating a memory leak.
除非我的逻辑在这里有缺陷,否则这会转化为:onPostExecute()
完全没用,因为如果您无法访问该方法在 UI 线程上运行有什么好处任何上下文?你不能在这里做任何有意义的事情.
Unless my logic is flawed here, this translates to: onPostExecute()
is entirely useless, because what good is it for this method to run on the UI thread if you don't have access to any context? You can't do anything meaningful here.
一种解决方法是不将上下文实例传递给 AsyncTask,而是传递一个 Handler
实例.这是有效的:由于 Handler 松散地绑定了上下文和任务,因此您可以在它们之间交换消息而不会冒泄漏的风险(对吧?).但这意味着 AsyncTask 的前提,即您不需要打扰处理程序,是错误的.这似乎也像是在滥用 Handler,因为您在同一个线程上发送和接收消息(您在 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).
最重要的是,即使采用了这种解决方法,您仍然会遇到这样的问题:当上下文被破坏时,您没有没有记录它触发的任务.这意味着您必须在重新创建上下文时重新启动任何任务,例如屏幕方向改变后.这既缓慢又浪费.
To top it all off, even with that workaround, you still have the problem that when the context gets destroyed, you have no record of the tasks it fired. That means that you have to re-start any tasks when re-creating the context, e.g. after a screen orientation change. This is slow and wasteful.
我对此的解决方案(如在 Droid-Fu 库中实施)是维护WeakReference
从组件名称到它们在唯一应用程序对象上的当前实例的映射.每当启动 AsyncTask 时,它都会在该映射中记录调用上下文,并且在每次回调时,它将从该映射中获取当前上下文实例.这确保您永远不会引用过时的上下文实例并且您始终可以访问回调中的有效上下文,以便您可以在那里进行有意义的 UI 工作.它也不会泄漏,因为引用是弱引用并且在给定组件的实例不再存在时被清除.
My solution to this (as implemented in the Droid-Fu library) is to maintain a mapping of WeakReference
s 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 库类进行子类化,这使得这是一种非常具有侵入性的方法.
Still, it is a complex workaround and requires to sub-class some of the Droid-Fu library classes, making this a pretty intrusive approach.
现在我只想知道:我只是大量遗漏了什么还是 AsyncTask 真的完全有缺陷?您使用它的经验如何?你是如何解决这些问题的?
Now I simply want to know: Am I just massively missing something or is AsyncTask really entirely flawed? How are your experiences working with it? How did you solve these problem?
感谢您的意见.
推荐答案
这样的事情怎么样:
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(...);
}
}
这篇关于AsyncTask 真的在概念上有缺陷还是我只是遗漏了什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!