Mat*_*ias 260 concurrency android handler android-asynctask
我已经调查了这个问题好几个月了,想出了不同的解决方案,我不满意,因为它们都是大规模的黑客攻击.我仍然无法相信一个设计有缺陷的课程已经进入框架而没有人在讨论它,所以我想我一定要错过一些东西.
问题在于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是否真的完全有缺陷?您的使用经验如何?你是怎么解决这些问题的?
感谢您的输入.
hac*_*bod 86
这样的事情怎么样:
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(...);
}
}
Run Code Online (Sandbox Code Playgroud)
Com*_*are 20
原因很明显:如果活动被破坏而触发任务怎么办?
手动取消活动与AsyncTaskin onDestroy().手动将新活动重新关联到AsyncTaskin onCreate().这需要静态内部类或标准Java类,加上可能有10行代码.
184*_*615 15
看起来AsyncTask是有点更不仅仅是概念上的缺陷.兼容性问题也无法使用它.Android文档阅读:
首次引入时,AsyncTasks在单个后台线程上串行执行. 从DONUT开始,这被改为一个线程池,允许多个任务并行运行. 启动HONEYCOMB,任务将恢复在单个线程上执行,以避免由并行执行引起的常见应用程序错误. 如果您真的想要并行执行,可以使用 executeOnExecutor(Executor, Params...) 此方法 的版本THREAD_POOL_EXECUTOR ; 但是,请在那里查看有关其使用的警告.
双方executeOnExecutor()并THREAD_POOL_EXECUTOR都在API级别11(的Android 3.0.x的,蜂巢).
这意味着如果您创建两个AsyncTasks来下载两个文件,则第二个下载将在第一个下载完成后才会开始.如果您通过两台服务器进行聊天,并且第一台服务器已关闭,则在连接到第一台服务器之前,您将无法连接到第二台服务器.(当然,除非您使用新的API11功能,但这会使您的代码与2.x不兼容).
如果你想要同时针对2.x和3.0+,这些东西变得非常棘手.
此外,文档说:
警告:使用工作线程时可能遇到的另一个问题是由于运行时配置更改(例如用户更改屏幕方向时)而导致活动意外重新启动,这可能会破坏您的工作线程.要了解如何在其中一次重新启动期间保留任务以及如何在销毁活动时正确取消任务,请参阅Shelves示例应用程序的源代码.
184*_*615 12
可能我们所有人,包括谷歌,都AsyncTask从MVC的角度来滥用.
Activity是一个Controller,控制器不应该启动可能超过View的操作.也就是说,AsyncTasks应该从Model中使用,来自未绑定到Activity生命周期的类 - 请记住,活动在轮换时被销毁.(对于View,你通常不会编译派生自android.widget.Button的类,但你可以.通常,你对View做的唯一一件事就是xml.)
换句话说,将AsyncTask衍生物放在Activities的方法中是错误的.OTOH,如果我们不能在活动中使用AsyncTasks,AsyncTask就失去了它的吸引力:它曾经被宣传为一个快速简单的解决方案.
我不确定通过引用AsyncTask中的上下文来冒险导致内存泄漏.
实现它们的通常方法是在Activity的一个方法的范围内创建一个新的AsyncTask实例.因此,如果活动被销毁,那么一旦AsyncTask完成就不会无法访问,然后有资格进行垃圾回收?因此,对活动的引用无关紧要,因为AsyncTask本身不会挂起.
| 归档时间: |
|
| 查看次数: |
33101 次 |
| 最近记录: |