史上最全系列之Android开发基础之AsyncTask
一、 什么是AsyncTask
AsyncTask暂且翻译为异步任务
概述:
AsyncTask使UI线程的使用变得恰当和简单。这个类允许在后台执行操作并且在UI线程呈现处理的结果,无需操作线程。
一个异步任务是指在后台运行的线程,其运行结果在UI线程呈现。
一个异步任务由3个泛型和4个步骤定义完成。
用法:
一个AsyncTask必须要被继承才能使用。这个子类必须重写doInBackground(Params...)方法,通常还要重写onPostExecute(Result)方法。
二、 AsyncTask介绍
在开发Android应用时必须遵守单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。在单线程模型中始终要记住两条法则:
1.不要阻塞UI线程
2.确保只在UI线程中访问Android UI工具包
当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线程。
比如说从网上获取一个网页,在一个TextView中将其源代码显示出来,这种涉及到网络操作的程序一般都是需要开一个线程完成网络访问,但是在获得页面源码后,是不能直接在网络操作线程中调TextView.setText()的因为其他线程中是不能直接访问主UI线程成员。
Android提供了几种在其他线程中访问UI线程的方法。
java代码:
Activity.runOnUiThread( Runnable )
View.post( Runnable )
View.postDelayed( Runnable, long )
Hanlder
这些类或方法同样会使你的代码很复杂很难理解。然而当你需要实现一些很复杂的操作并需要频繁地更新UI时这会变得更糟糕。
为了解决这个问题,Android 1.5提供了一个工具类:AsyncTask,它使创建需要与用户界面交互的长时间运行的任务变得更简单。不需要借助线程和Handler即可实现。
AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Params,Progress和Result。
Params 启动任务执行的输入参数,比如HTTP请求的URL。
Progress 后台任务执行的百分比。
Result 后台执行任务最终返回的结果,比如String。
AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,这些方法不应该由应用程序调用,开发者需要做的就是实现这些方法。
1) 子类化AsyncTask
2) 实现AsyncTask中定义的下面一个或几个方法
onPreExecute(), 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。
doInBackground(Params...),将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用publishProgress方法来更新实时的任 务进度。该方法是抽象方法,子类必须实现。
onProgressUpdate(Progress...),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread。
为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
1) Task的实例必须在UI thread中创建
2) execute方法必须在UI thread中调用
3) 不要手动的调用onPreExecute(),onPostExecute(Result),doInBackground(Params...),onProgressUpdate(Progress...)这几个方法
4) 该task只能被执行一次,否则多次调用时将会出现异常
三、AsyncTask和Handler对比
1
) AsyncTask实现的原理,和适用的优缺点
AsyncTask, 是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.
使用的优点:
简单,快捷
过程可控
使用的缺点 :
l 在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来.
2 )Handler异步实现的原理和适用的优缺点
在Handler 异步实现时,涉及到 Handler, Looper, Message,Thread四个对象,实现异步的流程是主线程启动Thread(子线程)àthread(子线程)运行并生成Message- àLooper获取Message并传递给HandleràHandler逐个获取Looper中的Message,并进行UI变更。
使用的优点:
结构清晰,功能定义明确
对于多个后台任务时,简单,清晰
使用的缺点:
在单个后台异步处理时,显得代码过多,结构过于复杂(相对性)
四、案例
main.xml
01
02
03
04
05
06
07
08
09
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
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:orientation
=
"vertical"
android:layout_width
=
"fill_parent"
android:layout_height
=
"fill_parent"
>
<
TextView
android:id
=
"@+id/textView01"
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
/>
<
ProgressBar
android:id
=
"@+id/progressBar02"
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
style
=
"?android:attr/progressBarStyleHorizontal"
/>
<
Button
android:id
=
"@+id/button03"
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
android:text
=
"更新progressbar"
/>
</
LinearLayout
>
|
01
02
03
04
05
06
07
08
09
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
|
public
class
MainActivity
extends
Activity {
private
Button button;
private
ProgressBar progressBar;
private
TextView textView;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.main);
button = (Button)findViewById(R.id.button03);
progressBar = (ProgressBar)findViewById(R.id.progressBar02);
textView = (TextView)findViewById(R.id.textView01);
button.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
ProgressBarAsyncTask asyncTask =
new
ProgressBarAsyncTask(textView, progressBar);
asyncTask.execute(
1000
);
}
});
}
}
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
//模拟网络环境
public
class
NetOperator {
public
void
operator(){
try
{
//休眠1秒
Thread.sleep(
1000
);
}
catch
(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
|
ProgressBarAsyncTask .java
01
02
03
04
05
06
07
08
09
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
/**
* 生成该类的对象,并调用execute方法之后
* 首先执行的是onProExecute方法
* 其次执行doInBackgroup方法
*
*/
public
class
ProgressBarAsyncTask
extends
AsyncTask<Integer, Integer, String> {
private
TextView textView;
private
ProgressBar progressBar;
public
ProgressBarAsyncTask(TextView textView, ProgressBar progressBar) {
super
();
this
.textView = textView;
this
.progressBar = progressBar;
}
/**
* 这里的Integer参数对应AsyncTask中的第一个参数
* 这里的String返回值对应AsyncTask的第三个参数
* 该方法并不运行在UI线程当中,主要用于异步操作,所有在该方法中不能对UI当中的空间进行设置和修改
* 但是可以调用publishProgress方法触发onProgressUpdate对UI进行操作
*/
@Override
protected
String doInBackground(Integer... params) {
NetOperator netOperator =
new
NetOperator();
int
i =
0
;
for
(i =
10
; i <=
100
; i+=
10
) {
netOperator.operator();
publishProgress(i);
}
return
i + params[
0
].intValue() +
""
;
}
/**
* 这里的String参数对应AsyncTask中的第三个参数(也就是接收doInBackground的返回值)
* 在doInBackground方法执行结束之后在运行,并且运行在UI线程当中 可以对UI空间进行设置
*/
@Override
protected
void
onPostExecute(String result) {
textView.setText(
"异步操作执行结束"
+ result);
}
//该方法运行在UI线程当中,并且运行在UI线程当中 可以对UI空间进行设置
@Override
protected
void
onPreExecute() {
textView.setText(
"开始执行异步线程"
);
}
/**
* 这里的Intege参数对应AsyncTask中的第二个参数
* 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行
* onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作
*/
@Override
protected
void
onProgressUpdate(Integer... values) {
int
vlaue = values[
0
];
progressBar.setProgress(vlaue);
}
}
|
四、注意事项
通常使用AsyncTask,是通过继承这个超类来完成的,如:
class BackgroundTaskextends AsyncTask { @Override protected Object doInBackground(Object... params){ return null; }}
复制代码
子类必须重载 doInBackground方法。“<>”里面的三个类型,依次代表执行参数类型、进度参数类型和结果参数类型。doInBackground的参数类型必须是执行参数类型,返回的类型必须和结果参数类型。这三个类型应该根据需要来定,其实用Object也可以,用 的时候在做类型转换。启动一个AsyncTask,可以在这样做:
BackgroudTask bt = newBackgroundTask();
bt.execute("param");
复制代码
使用AsyncTask的容易犯下的错误是在doInBackground方法里面直接对UI元素进行操作。如果需要和UI进行交互,可以配合使用publishProgress和onProgressUpdate。比如
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Override
protected
Object doInbackground(Object... params) {
... publishProgress(
"完成了20%"
);
... publishProgress(
"完成了80%"
);
...
return
null
;
}
protected
void
onProgressUpdate(Object... progress)
{ ... textView1.setText((String)progress[
0
]); ... }
|
复制代码
这里onProgressUpdate是工作在UI线程的。
使用AsyncTask的另一个问题是关于cancel。实际上,单单调用AsyncTask对象的cancel方法,并不能停止 doInBackground方法的继续执行。通常比较接受的方法是设置一个标志位,也就是在每次执行前检查一下某个变量的值(或者可以调用 isCancelled方法判断),来决定继续执行还是停止。这种处理手段对于一些循环性的工作比较有用,但是对于一些循环性弱的工作可能并不怎么有效。这也算是AsyncTask的一个弱点。和Thread相比,AsyncTask还有一个弱点是效率的问题,这个可以在本文开头给出的链接中找到相关的信 息。
AsyncTask还有一个问题和onPreExecute方法有关。这个方法是工作在UI线程的。虽然是叫 onPreExecute,但是doInBackground方法(也就是实际上的execute),并不会等待onPreExecute方法做完全部操 作才开始执行。所以,一般还是不要用这个方法,可以在调用AsyncTask对象的execute方法之前就把该完成的操作完成,以免引起某些错误。
AsyncTask还有一个方法是onPostExecute,这个方法也是工作在UI线程,它是在doInBackground方法执行结束,并返回 结果后调用。这个方法里面可以调用UI线程的startActivity,这样可以实现完成大量后台操作后,自动跳转Activity的功能。这个方法里面也可以执行另一个AsyncTask的execute方法。
六、Android怎样停止AsyncTask和Thread
我们知道在java的线程中,没有办法停止一个正在运行中的线程,在Android的AsyncTask中也是一样的。如果必须要停止一个线程,我们可以采用在这个线程中设置一个标志位,然后在线程run方法或AsyncTask的doInBackground方法中的关键步骤判断这个标志位以决定是否继 续执行。然后在需要终止此线程的地方改变这个标志位以达到停止线程的目的。
从外部调用AsyncTask的cancel方法并不能停止一个已经启动的AsyncTask,这个cancel方法的作用与线程的interrupt方 法相似,调用了一个线程的interrupt方法之后线程仍然运行,但是如果该线程的run方法里面调用过sleep的或者wait方法后,处于sleep或wait状态,则sleep和wait立即结束并且抛出InterruptedException异常。AsyncTask的cancel方 法也一样,如果在这个Task的doInBackground方法中调用了sleep或wait方法,当在UI线程中调用了这个Task实例的 cancel方法之后,sleep或wait立即结束并且抛出InterruptedException异常,但是如果捕获该异常的代码后面还有其他代码,则这些代码还会继续执行。测试代码如下:
01
02
03
04
05
06
07
08
09
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
|
public
class
AsyncTaskTest
extends
Activity {
/** Called when the activity is first created. */
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.main);
// set the six buttons listener
Button startButton = (Button)
this
.findViewById(R.id.StartTask);
final
TestAsyncTask task =
new
TestAsyncTask(
0
);
startButton.setOnClickListener(
new
OnClickListener() {
public
void
onClick(View v) {
task.execute(
"str1"
,
"str2"
);
}
});
Button endButton = (Button)
this
.findViewById(R.id.StopTask);
endButton.setOnClickListener(
new
OnClickListener() {
public
void
onClick(View v) {
task.cancel(
false
);
}
});
Button startSleepButton = (Button)
this
.findViewById(R.id.StartThread_sleep);
final
ThreadForTestSleep threadForTestSleep =
new
ThreadForTestSleep();
startSleepButton.setOnClickListener(
new
OnClickListener() {
public
void
onClick(View v) {
threadForTestSleep.start();
}
});
Button endSleepButton = (Button)
this
.findViewById(R.id.StopThread_sleep);
endSleepButton.setOnClickListener(
new
OnClickListener() {
public
void
onClick(View v) {
threadForTestSleep.interrupt();
}
});
Button startWaitButton = (Button)
this
.findViewById(R.id.StartThread_wait);
final
ThreadForTestWait threadForTestWait =
new
ThreadForTestWait();
startWaitButton.setOnClickListener(
new
OnClickListener() {
public
void
onClick(View v) {
threadForTestWait.start();
}
});
Button endWaitButton = (Button)
this
.findViewById(R.id.StopThread_wait);
endWaitButton.setOnClickListener(
new
OnClickListener() {
public
void
onClick(View v) {
threadForTestWait.interrupt();
}
});
}
/**
* AsyncTask
*
* @author alex
*
*/
private
class
TestAsyncTask
extends
AsyncTask<String, Integer, Double> {
double
a;
public
TestAsyncTask(
double
a) {
this
.a = a;
}
@Override
protected
Double doInBackground(String... params) {
for
(String param : params) {
Log.i(
"TestAsyncTask"
,
"param:"
+ param);
}
Log.i(
"TestAsyncTask"
,
"doInBackground is start"
);
for
(
int
i =
0
; i <
10000000
; i++) {
a = i * i + i;
Log.d(
"-----"
,
"a:"
+ a);
}
Log.i(
"TestAsyncTask"
,
"sleep 1 is end"
);
try
{
Thread.sleep(
30000
);
}
catch
(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.i(
"TestAsyncTask"
,
"sleep 2 is end and continue execute"
);
return
a;
}
protected
void
onPostExecute(Double result) {
Log.i(
"last a value is"
,
""
+ result);
}
}
/**
* test sleep
*
* @author Administrator
*
*/
private
class
ThreadForTestSleep
extends
Thread {
public
void
run() {
Log.i(
"ThreadForTestWait"
,
"sleep start"
);
try
{
sleep(
30000
);
}
catch
(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
double
a;
for
(
int
i =
0
; i <
10000000
; i++) {
a = i * i + i;
Log.d(
"-----"
,
"a:"
+ a);
}
Log.i(
"ThreadForTestWait"
,
"sleep end"
);
}
}
/**
* test wait
*
* @author Administrator
*
*/
private
class
ThreadForTestWait
extends
Thread {
public
void
run() {
Log.i(
"ThreadForTestWait"
,
"wait start"
);
try
{
synchronized
(
this
) {
wait();
}
}
catch
(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.i(
"ThreadForTestWait"
,
"wait end"
);
}
}
}
|
我们来看看这个例子怎么样,这里主要用到了view.View.OnClickListener;监听,android.widget.Button按钮,我们定义一个Button是开始,一个Button定义为停止。这样我们就可以给按钮加上一个监听,在监听里定义当点击按钮时,就可以 停止AsyncTask和Thread,这个方法我个人感觉非常的好。这个主要是加了一个sleep(30000);这样的话,我们就有时间来判断一下是否应该怎么样做。