本节介绍AsyncTask的使用方法与风险
link


AsyncTask Basics

AsyncTask是一把双刃剑,用的好了无往不利,用得不好则伤人伤己。

考虑下面这种场景,有一个非常耗时的操作需要进行,我们在UI Thread之外启动了一个Worker Thread,当Worker Thread完成它的工作时,会把计算结果返回给UI Thread

由于这种应用场景太常见了,Android为我们提供了AsyncTask这一机制。AsyncTask有三个重要的函数。

  • onPreExecute(),在UI Thread中执行,准备Task
  • doInBackground(),在Worker Thread中执行复杂的任务
  • onPostExecute(),在UI Thread中执行,处理任务执行结果


特别说明!onPreExecute并不保证在UI线程中执行!

虽然官方API文档中的说明写到AsyncTask“必须在UI线程中执行”,但Framework底层实现中并没有保证这一机制

The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.

换句话说,“保证onPreExecute()在UI线程中执行”是一个编程规范,而并非强制要求。相比之下,onPostExecue()一定是在UI线程中执行的,因为它是用的是mainLooper。

AsyncTask.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
   private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}

private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}

private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}

@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}

关于上面这一点,可以参考这个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class MainActivity extends AppCompatActivity {

private static String TAG = "mainactivity";

View titleView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
new Thread(new Runnable() {
@Override
public void run() {
MyAsyncTask task = new MyAsyncTask();
task.execute();
}
}).start();
}

private void initViews() {
titleView = findViewById(R.id.title);
titleView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toast("Hello World!");
}
});
}

private void toast(String s) {
Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
}

class MyAsyncTask extends AsyncTask {
@Override
protected void onPreExecute() {
checkRunningInMainThread("onPreExecute");
super.onPreExecute();
}

@Override
protected void onProgressUpdate(Object[] values) {
checkRunningInMainThread("onProgressUpdate");
super.onProgressUpdate(values);
}

@Override
protected void onPostExecute(Object o) {
checkRunningInMainThread("onPostExecute");
super.onPostExecute(o);
}

@Override
protected Object doInBackground(Object[] params) {
checkRunningInMainThread("doInBackGround");
return null;
}
}

private static void checkRunningInMainThread(String methodName) {
Log.d(TAG, methodName + " is running in UI Thread? " + (Looper.myLooper() != null && Looper.getMainLooper() == Looper.myLooper()));
}
}

例子中有一些无关代码(titleView),不过不影响阅读。

日志输出如下:

1
2
3
02-29 20:29:06.440 13212-13239/com.leili.geeker D/mainactivity: onPreExecute is running in UI Thread? false
02-29 20:29:06.443 13212-13241/com.leili.geeker D/mainactivity: doInBackGround is running in UI Thread? false
02-29 20:29:06.850 13212-13212/com.leili.geeker D/mainactivity: onPostExecute is running in UI Thread? true

印证了onPreExecute()并不保证在UI线程中执行。


AsyncTask May Get You Into A Trouble

有了上面三个函数,似乎AsyncTask用起来并没有什么困难。然而,如果使用不当,AsyncTask会产生很多匪夷所思的问题。

首先,AsyncTask处理的所有Task,都是放在同一个队列中依次进行的。这意味着,如果前面的Task相当耗时,会使得它后面的Task在很久之后才得以执行。在你处理long running task时应给予高度关注。

实际上,AsyncTask提供了一个用来并行执行Task的方法executeOnExecutor,然而光头哥建议,当你足够理解这个方法时,你就会觉得还是不用为好。


AsyncTask Canceling

AsyncTask在执行之前/执行过程中,是可以被Cancel掉的。参数mayInterruptIfRunning决定了是否允许在执行过程中被中断。如果一项任务执行了cacel方法,那么在doInBackground后被调起的将不再是onPostExecute,而是onCancel,同样是在UI Thread中进行。

1
2
3
4
5
/*
* Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
* Calling this method will result in onCancelled(Object) being invoked on the UI thread after doInBackground(Object[]) returns. Calling this method guarantees that onPostExecute(Object) is never invoked. After invoking this method, you should check the value returned by isCancelled() periodically from doInBackground(Object[]) to finish the task as early as possible.
*/
public final boolean cancel (boolean mayInterruptIfRunning);

如果你的Task在设计时,考虑了cancel的情况,那么你还需要额外做两件事。

  1. Check a “canceled” flag regularly
  2. Report work results invalid

对于1,如下

1
2
3
4
5
6
doInBackground(..) {
// Doing some stuff
if (isCanceled()) {..} // Oh noez, we done, clean up
for (i < obj.length)
if (isCanceled()) {..} // Oh noez, we done, clean up
}

对于2,当我们知道被cancel的task会调用onCancel方法时,可以在onCancel中做一些诸如更新UI、清理内存的事情。


===Ending===