很好的一篇文章,非常适合初学者了解 AsyncTask,居然百度不出来。这篇博文是从俄语翻译成英语的,再从英语二次翻译过来。
作者:+Fré Dumazy(?)
原文链接:http://bon-app-etit.blogspot.jp/2013/04/the-dark-side-of-asynctask.html
AsyncTask的黑暗面
大家好,在这篇博文里我会向大家介绍一些 AsyncTask少有人知的黑暗面。半年前我第一次发文介绍 AsyncTask的用法。现在我要告诉你们这些用法可能会导致什么样的问题,以及如何解决这些问题。
AsyncTask
AsyncTask是在 Android1.5版本加入的,初衷是为了方便开发者管理线程。早在 Android1.0和1.1版本,AsyncTask也被称为 UserTask。你可以在你的工程源码里直接替换 AsyncTask为 UserTask以便支持低版本,但根据 Android官方统计,目前只有不到0.1%的设备运行在这些低版本上,你完全可以选择不支持低版本。
现在使用 AsyncTask可能是最常用的 Android后台处理方法。它确实很容易上手,但这个类存在着一些容易被忽略的问题。
LifeCycle
这是 AsyncTask很容易被误解的一点。程序猿们可能认为当创建 AsyncTask的 Activity被销毁时,AsyncTask对象也会被销毁。但通常情况下,AsyncTask会继续执行它的 doInBackGround方法直到结束。AsyncTask的结束有两种情况:1. 外部调用了 cancel方法,会执行 onCancelled;2. 正常执行完成并调用 onPostExecute方法。
假设 AsyncTask在 Activity销毁之前没有被取消,这可能会引起 AsyncTask的 crash,原因是 AsyncTask里所引用的 Activity中的 View等对象已经不存在了。所以我们需要确保 Activity销毁时调用了 AsyncTask的 cancel方法。cancel方法需要传入一个 boolean参数(mayInterruptIfRunning),来标识是否打断正在执行的 task。如果设置为 false,那么即使调用了 cancel方法,正在执行中的 AsyncTask也会执行完成。如果你的 doInBackGround方法中有循环语句的话,你需要在每次循环的时候都检查一下这个标志位。
因此,我们应当确保使用正确的方法来取消 AsyncTask。
Does cancel() really work?
简而言之:有时候会。
如果使用 cancel(false),正在执行中的 AsyncTask会一直执行直到完成,但这个方法可以阻止 onPostExecute方法被调用。因此很多情况下使用 cancel(false)是没有意义的(注:我们做 cancel操作不只是阻止 onPostExecute中的更新,也非常希望能立即停止 doInBackGround中那些开销较大的动作)。那你可能会想,无论如何都使用 cancel(true)不就可以了?错,如果 mayInterruptIfRunning标志位被置为 true,那只代表系统会尽可能早的结束 AsyncTask,如果在 doInBackGround中的操作是不可打断的,那 cancel(true)实际上也起不到立即结束 AsyncTask的功能。
Memory leaks
AsyncTask的 doInBackGround方法运行在后台线程,而其他一些方法运行在主线程,因此在 AsyncTask的生命周期内会始终保留一个对 Activity的引用,即便是 Activity已经被销毁,但在 AsyncTask中还是保留着引用。这可能会导致内存泄露。
Losing your results
另一个问题是随着 Activity的重启,我们可能会失去 AsyncTask的运行结果。例如横竖屏切换时,Activity被销毁然后重建,但 AsyncTask中存有的还是原来被销毁的那个 Activity的引用,所以 onPostExecute方法会不起作用。解决方法是,你可以在 Application等不会被销毁的对象中创建一个 AsyncTask。Activity.onRetainNonConfigurationInstance()和 Fragment.setRetainedInstance(true) 可能也会对这种情况有所帮助。
Serial or parallel?
很多人都对 AsyncTask是并行还是串行心存疑惑,原因是 AsyncTask的运行方式随着 Android版本的不同变更过几次。你可能会质疑我所说的“AsyncTask可能是并行或是串行”。假设你的方法中有如下两行代码:
new AsyncTask1().execute(); new AsyncTask2().execute();
Task2会与 Task1同时开始吗?还是 Task2会在 Task1结束之后才开始运行?
答案是:依赖于它们编译的 Android版本。
在 Android1.6之前:
第一版的 AsyncTask是串行的,意味着只有前一个 Task结束后下一个 Task才能开始运行。这可不是一种好的方式。
Android1.6至 Android2.3:
Android开发团队决定把 AsyncTask的运行方式改为并行,这样多个 AsyncTask可以同时运行在不同的后台线程中。这有一个问题,很多开发者已经习惯于串行的方式,这种改变会导致很多并发问题。
Android3.0至今:
“嘿嘿,他们习惯并发了?那我们改回去~!Let's Rock...” AsyncTask又被改为串行方式了。当然 Android开发团队也提供了并行的方式,就是使用 This is done by the method executeOnExecutor(Executor)方法,可以去 API文档中了解更多有关这个方法的信息。
如果我们想自己控制运行方式,保证 AsyncTask始终并行,那可以用以下的代码(API level1~3不适用):
public static void execute(AsyncTask as) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR1) { as.execute(); } else { as.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } }
Do we need AsyncTask?
并不是这样的。用少量代码实现后台运行实际上并不困难,但我们也知道要想不出错的话需要考虑的非常周全。另一种执行后台操作的方法是使用 Loaders。这个类在 Android3.0被引入,也可以通过使用 library导入。我可能今后还会在写一篇关于如何使用 Loaders的博文,所以多关注我的博客吧:) (最后一句是作者的原话 哈哈)