AsyncTask
# 1. 相关概念
# 1.1 同步与异步的概念
同步:当我们执行某个功能时,在没有得到结果之前,这个调用就不能返回,一直处于等待阻塞状态。
异步:和同步则是相对的,当我们执行某个功能后,我们并不需要立即得到结果,我们额可以正常地 做其他操作,这个功能可以在完成后通知或者回调来告诉我们;还是上面那个后台下载的例子,后台下载, 我们执行下载功能后,我们就无需去关心它的下载过程,当下载完毕后通知我们就可以了
# 1.2 作用
- 实现多线程。在工作线程中执行任务,如 耗时任务
- 异步通信、消息传递,实现工作线程 & 主线程(
UI
线程)之间的通信。即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI
操作
# 优点
- 方便实现异步通信。不需使用 “任务线程(如继承
Thread
类) +Handler
”的复杂组合 - 节省资源。采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销
# 1.3 Android为什么要引入异步任务
# Main Thread 和 Worker Thread
在Android当中,通常将线程分为两种,一种叫做Main Thread,除了Main Thread之外的线程都可称为Worker Thread。Android程序刚启动时,会同时启动一个对应的主线程(Main Thread),这个主线程主要负责处理 与UI相关的事件!有时我们也把他称作UI线程!
当一个应用程序运行的时候,Android操作系统就会给该应用程序启动一个线程,这个线程就是我们的Main Thread,这个线程非常的重要,它主要用来加载我们的UI界面,完成系统和我们用户之间的交互,并将交互后的结果又展示给我们用户,所以Main Thread又被称为UI Thread。
Android系统默认不会给我们的应用程序组件创建一个额外的线程,所有的这些组件默认都是在同一个线程中运行。然而,某些时候当我们的应用程序需要完成一个耗时的操作的时候,例如访问网络或者是对数据库进行查询时,此时我们的UI Thread就会被阻塞。例如,当我们点击一个Button,然后希望其从网络中获取一些数据,如果此操作在UI Thread当中完成的话,当我们点击Button的时候,UI线程就会处于阻塞的状态,此时,我们的系统不会调度任何其它的事件,更糟糕的是,当我们的整个现场如果阻塞时间超过5秒钟(官方是这样说的),这个时候就会出现 ANR (Application Not Responding)的现象,此时,应用程序会弹出一个框,让用户选择是否退出该程序。对于Android开发来说,出现ANR的现象是绝对不能被允许的。
另外,由于我们的Android UI控件是线程不安全的,所以我们不能在UI Thread之外的线程当中对我们的UI控件进行操作。因此在Android的多线程编程当中,我们有两条非常重要的原则必须要遵守:
- 绝对不能在UI Thread当中进行耗时的操作,不能阻塞我们的UI Thread
- 不能在UI Thread之外的线程当中操纵我们的UI元素
在Android App时我们必须遵守这个单线程模型的规则: Android UI操作并不是线程安全的并且这些操作都需要在UI线程中执行!
- 假如我们在非UI线程中,比如在主线程中new Thread()另外开辟一个线程,然后直接在里面修改UI控件的值;此时会抛出下述异常:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views
- 还有一点,如果我们把耗时的操作都放在UI线程中的话,如果UI线程超过5s没有响应用于请求,那么 这个时候会引发ANR(Application Not Responding)异常,就是应用无响应~
- 最后还有一点就是:Android 4.0后禁止在UI线程中执行网络操作~不然会报:
android.os.NetworkOnMainThreadException
简单概括就是:
- Android是单线程模型,只能在主线程中更新UI
- UI线程不要做耗时操作,交由子线程来做,否则会ANR
- Android 4.0后禁止在UI线程中执行网络操作
以上的种种原因都说明了Android引入异步任务的意义,当然实现异步也不可以不用到我们本节讲解 的AsyncTask,我们可以自己开辟一个线程,完成相关操作后,通过下述两种方法进行UI更新:
- Handler机制,我们在Handler里写好UI更新,然后通过sendMessage()等的方法通知UI 更新,另外别忘了Handler写在主线程和子线程中的区别
- 利用
Activity.runOnUiThread(Runnable)
把更新ui的代码创建在Runnable中,更新UI时,把Runnable对象传进来即可
# 2. AsyncTask解析
# 2.1 为什么要用AsyncTask
我们可以用上述两种方法来完成我们的异步操作,如果我们写的异步操作比较多,或者较为繁琐,难道我们new Thread()然后用上述方法通知UI更新么?这样显得略微繁琐。官方给我们提供了AsyncTask这个封装好的轻量级异步类。
相比起Handler,AsyncTask显得更加简单。AsyncTask暂时可以满足初学者的需求,但是到了公司真正做项目以后,我们更多的使用第三发的框架,比如Volley,OkHttp,android-async-http,XUtils等很多,但是掌握AsyncTask还是很有必要的!
异步任务由在后台线程上运行且其结果发布在 UI 线程上的计算定义。异步任务是由3种一般类型,称为定义Params
,Progress
和Result
,和4个步骤,称为onPreExecute
,doInBackground
, onProgressUpdate
和onPostExecute
。
和handler一样是用于处理异步任务的,不过对于前者,后者的代码更为轻量级,其实后台一个线程池,在异步任数据比较底大的时候,AsyncTask的线程池结构的优势就体现出来了
# 2.2 官方声明
AsyncTask类在 API 级别 30 中已弃用。
AsyncTask旨在实现UI线程的正确和简单使用。然而,最常见的用例是集成到UI中,这将导致上下文泄漏、错过回调或配置更改时崩溃。它在不同版本的平台上也有不一致的行为,从doInBackground
接受异常,并且在直接使用Executor
时没有提供太多的实用性。
AsyncTask被设计成一个围绕Thread
和Handler
的助手类,并不构成通用线程框架。AsyncTasks理想情况下应用于短期操作(最多几秒钟)。如果需要让线程长时间运行,强烈建议您使用java.util.concurrent
包提供的各种API,,例如Executor
、 ThreadPoolExecutor
和FutureTask
。
# 2.3 AsyncTask的基本结构
# 2.3.1 使用说明
AsyncTask
类属于抽象类,即使用时需 实现子类
public abstract class AsyncTask<Params, Progress, Result> {
...
}
2
3
类中参数为3种泛型类型
整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
具体说明:
- Params:开始异步任务执行时传入的参数类型,对应
excute()
中传递的参数 - Progress:异步任务执行过程中,返回下载进度值的类型
- Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
- 使用时并不是所有类型都被使用
- 若无被使用,可用java.lang.Void类型代替
- 若有不同业务,需额外再写1个AsyncTask的子类
# 2.3.2 核心方法
# 2.3.3 四个步骤
当一个异步任务被执行时,任务会经过 4 个步骤:
onPreExecute()
,在执行任务之前在 UI 线程上调用。此步骤通常用于设置任务,例如通过在用户界面中显示进度条。doInBackground(Params...)
,onPreExecute()
执行完成后立即在后台线程上调用。此步骤用于执行可能需要很长时间的后台计算。异步任务的参数传递到这一步。计算的结果必须由这一步返回,并将传递回最后一步。此步骤还可用于publishProgress(Progress...)
发布一个或多个进度单元。这些值在onProgressUpdate(Progress...)
步骤中的 UI 线程上发布 。onProgressUpdate(Progress...)
, 调用后在 UI 线程上调用publishProgress(Progress...)
。执行的时间是不确定的。此方法用于在后台计算仍在执行时在用户界面中显示任何形式的进度。例如,它可用于动画进度条或在文本字段中显示日志。onPostExecute(Result)
, 在后台计算完成后在 UI 线程上调用。后台计算的结果作为参数传递给这一步。
为什么我们的AsyncTask抽象类只有一个 doInBackground 的抽象方法呢??原因是,我们如果要做一个异步任务,我们必须要为其开辟一个新的Thread,让其完成一些操作,而在完成这个异步任务时,我可能并不需要弹出要给ProgressDialog,我并不需要随时更新我的ProgressDialog的进度条,我也并不需要将结果更新给我们的UI界面,所以除了 doInBackground 方法之外的三个方法,都不是必须有的,因此我们必须要实现的方法是 doInBackground 方法。
# 2.3.4 取消任务
首次引入时,AsyncTasks 在单个后台线程上串行执行。 从 Build.VERSION_CODES.DONUT 开始,这已更改为允许多个任务并行操作的线程池。 从 Build.VERSION_CODES.HONEYCOMB 开始,任务在单个线程上执行,以避免并行执行导致的常见应用程序错误。
如果你真的想要并行执行,你可以用 THREAD_POOL_EXECUTOR 调用 executeOnExecutor(java.util.concurrent.Executor, java.lang.Object[])
。
# 2.3.5 方法执行顺序如下
# 2.3.5 注意事项
- Task的实例必须在UI thread中创建;
- execute方法必须在UI thread中调用;
- 不要手动的调用onPreExecute(),onPostExecute(Result),doInBackground(Params...),onProgressUpdate(Progress...)这几个方法;
- 该task只能被执行一次,否则多次调用时将会出现异常;
# 3. 使用步骤
AsyncTask的使用步骤有3个:
- 创建
AsyncTask
子类 & 根据需求实现核心方法 - 创建
AsyncTask
子类的实例对象(即 任务实例) - 手动调用
execute()
从而执行异步线程任务
具体介绍如下:
步骤1:创建AsyncTask子类
- 继承AsyncTask类
- 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
- 根据需求,在AsyncTask子类内实现核心方法
private class MyTask extends AsyncTask<Params, Progress, Result> {
// 方法1:onPreExecute()
// 作用:执行 线程任务前的操作
// 注:根据需求复写
@Override
protected void onPreExecute() {
// TODO
}
// 方法2:doInBackground()
// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
// 注:必须复写,从而自定义线程任务
@Override
protected String doInBackground(String... params) {
// TODO: 自定义的线程任务
// 可调用publishProgress() 显示进度, 之后将执行onProgressUpdate()
publishProgress(count);
}
// 方法3:onProgressUpdate()
// 作用:在主线程 显示线程任务执行的进度
// 注:根据需求复写
@Override
protected void onProgressUpdate(Integer... progresses) {
// TODO
}
// 方法4:onPostExecute()
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
// 注:必须复写,从而自定义UI操作
@Override
protected void onPostExecute(String result) {
// UI操作,回调返回执行结果
}
// 方法5:onCancelled()
// 作用:将异步任务设置为:取消状态
@Override
protected void onCancelled() {
}
}
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
【步骤2】:创建AsyncTask子类的实例对象(即 任务实例)
- AsyncTask子类的实例必须在UI线程中创建
MyTask mTask = new MyTask();
【步骤3】:手动调用execute(Params... params) 从而执行异步线程任务
- 必须在UI线程中调用
- 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
- 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
- 不能手动调用上述方法
mTask.execute();
# 4. 实例一:显示加载资源任务的进度
- 点击按钮 则 开启线程执行线程任务
- 显示后台加载进度
- 加载完毕后更新UI组件
- 期间若点击取消按钮,则取消加载
public class MainActivity extends AppCompatActivity {
// 主布局中的UI组件
private Button button, cancel; // 加载、取消按钮
private TextView text; // 更新的UI组件
private ProgressBar progressBar; // 进度条
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
// AsyncTask子类的实例必须在UI线程中创建
MyAsyncTask mTask = new MyAsyncTask();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTask.execute();
}
});
cancel = (Button) findViewById(R.id.cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 取消一个正在执行的任务,onCancelled方法将会被调用
mTask.cancel(true);
}
});
}
private void initView() {
button = (Button) findViewById(R.id.button);
cancel = (Button) findViewById(R.id.cancel);
text = (TextView) findViewById(R.id.text);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
}
/**
* 步骤1:创建AsyncTask子类
* 注:
* a. 继承AsyncTask类
* b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
* 此处指定为:输入参数 = String类型、执行进度 = Integer类型、执行结果 = String类型
* c. 根据需求,在AsyncTask子类内实现核心方法
*/
private class MyAsyncTask extends AsyncTask<String, Integer, String> {
// 作用:执行 线程任务前的操作
@Override
protected void onPreExecute() {
text.setText("加载中");
// 执行前显示提示
}
// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
// 此处通过计算从而模拟“加载进度”的情况
@Override
protected String doInBackground(String... strings) {
try {
int count = 0;
int length = 1;
while (count < 99) {
count += length;
// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
publishProgress(count);
// 模拟耗时任务
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
// 作用:在主线程 显示线程任务执行的进度
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressBar.setProgress(values[0]);
text.setText("loading..." + values[0] + "%");
}
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
@Override
protected void onPostExecute(String result) {
// 执行完毕后,则更新UI
text.setText("加载完毕");
}
// 作用:将异步任务设置为:取消状态
@Override
protected void onCancelled() {
text.setText("已取消");
progressBar.setProgress(0);
}
}
}
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
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
如果点击取消再点击下载,就会出现 非法状态异常,因为AsyncTask只能创建一个实例
# 5. 获取AsyncTask返回值
在某个方法/类需要耗时操作,而当耗时操作结束后需要返回某个值或信号,实现方法有很多,可以通过Handler、AsyncTask等把值返回给UI线程,此文则通过定义内部自定义监听接口及Handler,实现回调数据。
# 回调参数通过自定义监听事件
首先创建回调接口
public interface OnDataListener {
void onDataSuccess(String data);
void onDataFailed(String error);
}
2
3
4
在AsyncTask中通过此接口获取到onPostExecute中的数值
public class MyAsyncTask extends AsyncTask<Void, Void, String> {
private OnDataListener listener;
public void setOnDataListener(OnDataListener asyncResponse) {
this.listener = asyncResponse;
}
@Override
protected String doInBackground(Void... voids) {
return responseData;
}
@Override
protected void onPostExecute(String msg) {
super.onPostExecute(msg);
if (msg != null | msg.length() != 0) {
if (responseCode == 200) {
listener.onDataSuccess(msg); // 任务完成时执行此方法
} else {
listener.onDataFailed(msg + " " + responseCode); // 任务失败执行此方法
}
} else {
msg = "网络无法请求";
}
}
}
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
在Activity或者Fragment中实现此接口即可
@Override
public void onClick(View v) {
// 提交异步任务
MyAsyncTask asyncTask = new MyAsyncTask(sb.toString());
asyncTask.execute();
// 通过自定义的接口回调获取AsyncTask中onPostExecute返回的结果变量
asyncTask.setOnDataListener(new OnDataListener() {
// 请求成功后回调,得到成功执行的结果
@Override
public void onDataSuccess(String data) {
JSONFormat format = new JSONFormat();
mTextView.setText(format.printJson(data));
}
// 请求失败后回调,返回错误码
@Override
public void onDataFailed(String error) {
mTextView.setText("Error Code is" + error);
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 使用Handler
public class ImageViewAsyncTask extends AsyncTask<String, Integer, Bitmap> {
String mUrl;
Handler mHandler;
public ImageViewAsyncTask(String url,Handler handler){
this.mUrl = url;
this.mHandler = handler;
}
@Override
protected Bitmap doInBackground(String... params) {
InputStream ins = null;
Bitmap bitmap = null;
try {
URL url = new URL(mUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if(connection.getResponseCode()==HttpURLConnection.HTTP_OK){
ins = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(ins);
return bitmap;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(ins!=null){
try {
ins.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
Message msg = mHandler.obtainMessage();
if(result!=null){
msg.what = 1;
msg.obj = result;
} else{
msg.what = 2;
}
mHandler.sendMessage(msg);
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
}
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
调用方式
ImageViewAsyncTask task2 = new ImageViewAsyncTask("http://static.oschina.net/uploads/ad/new_banner_one_ronglianyun_WrqUs.png", handler);
task2.execute();
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Bitmap bitmap = (Bitmap) msg.obj;
im2.setImageBitmap(bitmap);
break;
default:
break;
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 6. 实例二:AsyncTask请求网络数据
HttpURLConnection、AsyncTask配合接口实现请求网络任务
聚合数据接口:
请求地址:http://japi.juhe.cn/qqevaluate/qq
请求参数:qq=2726109782&key=e6ce0de3a1db3739d4343b9d19a9d61f
请求方式:GET
2
3
# 创建接口
网络请求是异步的,而且必须在子线程使用,稍有点麻烦。但是我们可以使用AsyncTask完成任务后调用接口的方式执行代码操作。
先创建一个接口:HttpGetDataListener.java
public interface HttpGetDataListener {
void getData(String data);
}
2
3
# 创建AsyncTask
- 在doInBackground()方法中执行网络请求
- onPostExecute()方法中使用接口要执行的方法
MyAsyncTask负责网络请求
public class MyAsyncTask extends AsyncTask<Void, Void, String> {
private OnDataListener listener;
private String mUrl;
int responseCode = 0;
public MyAsyncTask(String url) {
this.mUrl = url;
}
public void setOnDataListener(OnDataListener asyncResponse) {
this.listener = asyncResponse;
}
@Override
protected String doInBackground(Void... voids) {
HttpURLConnection connection = null;
BufferedReader reader = null;
String responseData = "";
try {
URL url = new URL(mUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(3000);
connection.setConnectTimeout(3000);
InputStream in = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
responseData = sb.toString();
responseCode = connection.getResponseCode();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭连接,防止内存泄漏
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (connection != null) {
connection.disconnect();
}
}
return responseData;
}
@Override
protected void onPostExecute(String msg) {
super.onPostExecute(msg);
if (msg != null | msg.length() != 0) {
if (responseCode == 200) {
listener.onDataSuccess(msg); // 任务完成时执行此方法
} else {
listener.onDataFailed(msg + " " + responseCode);
}
} else {
msg = "网络无法请求";
}
}
}
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
65
66
MainActivity 存入URL
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView mTextView;
private Button mButton;
private String url = "http://japi.juhe.cn/qqevaluate/qq";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mTextView = findViewById(R.id.text_content);
mButton = findViewById(R.id.net_button);
mButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// url参数拼接
StringBuilder sb = new StringBuilder("http://japi.juhe.cn/qqevaluate/qq");
sb.append("?qq=");
sb.append(URLEncoder.encode("1527147030"));
sb.append("&key=");
sb.append(URLEncoder.encode("e6ce0de3a1db3739d4343b9d19a9d61f"));
Log.i("TAG", "拼接URL >>> " + sb.toString());
// 提交异步任务
MyAsyncTask asyncTask = new MyAsyncTask(sb.toString());
asyncTask.execute();
asyncTask.setOnDataListener(new OnDataListener() {
@Override
public void onDataSuccess(String data) {
JSONFormat format = new JSONFormat();
mTextView.setText(format.printJson(data));
}
@Override
public void onDataFailed(String error) {
mTextView.setText("Error Code is" + error);
}
});
}
}
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
# 7. 实例三:加载网络图片
使用AsyncTask加载网络图片
自定义封装ImageView参考🔗https://blog.csdn.net/qq_33200967/article/details/77263062 (opens new window)
原文使用Handler+Thread的方式来加载网络图片,ImageView做了自定义封装,传入URL即可加载该图片
我将Thread 子线程联网,通过Handler设置图片的方式改为了用AsyncTask,二者效果等价。AsyncTask实质上就是将线程任务放到线程池来处理。
【文章参考】
[1] CSDN作者-hnyzwtf. https://blog.csdn.net/hnyzwtf/article/details/51099075.