1.简介
AsyncTask,是除了Handler之外,Android提供给我们方便地在子线程中对UI进行操作的另一个工具。借助AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程。当然,AsyncTask背后的实现原理也是基于异步消息处理机制的,只是Android帮我们做了很好的封装而已。
AsyncTask属于已经封装好的轻量级异步类,主要的功能仍然是多线程通信以及消息处理。其优点主要为:
- 方便实现异步通信:不需要使用 “任务线程(如继承
Thread
类) +Handler
”的复杂组合 - 节省资源:采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销。
下面我们再来看看AsyncTask拥有的一些特性。
2.特性
在菜鸟教程中对于AsyncTask的介绍中,写得较为详细,这里就直接贴出部分说明图,供读者参考:
首先,AsyncTask是一个抽象类,一般我们都会定义一个类继承AsyncTask然后重写相关方法,其构造方法的参数说明如图所示:
接下来,AsyncTask中相应方法的执行流程如下:
最后,再给出AsyncTask的使用注意事项,如下所示:
直接说明这些概念可能会让初次学习AsyncTask的读者觉得费解,接下来就会演示AsyncTask最经典的应用案例:进度条来说明AsyncTask的使用方法。
3.演示
3.1 集成
AsyncTask和Handler一样,默认就是集成在Android SDK(具体来说,在Android 1.5版本之后)中的,所以这一步可以省略,你可以在代码中直接使用AsyncTask来处理消息机制。
3.2 布局文件
为了实现进度条的功能,在布局文件activity_main.xml中,我们定义一个按钮控件,用来触发进度条的运动,然后再定义一个进度条控件(ProgressBar),最后再定义一个文本控件,记录进度条的进度值,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<ProgressBar
android:id="@+id/pb_test"
android:layout_width="match_parent"
android:layout_height="50dp"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:max="100"
android:progress="0"/>
<Button
android:id="@+id/btn_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启动进度条"/>
<TextView
android:id="@+id/tv_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="100sp"
android:gravity="center"
android:text="进度值"/>
</LinearLayout>
布局效果大致如下:
3.3 创建AsyncTask子类
前面说过,AsyncTask是一个抽象类。要想使用,就需要先创建一个集成它的子类。这里新建一个名为ProgressAsyncTask的AsyncTask子类,然后重写相应的方法,代码如下:
/**
* AsyncTask<Params, Progress, Result>三个泛型参数
* Params
* 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。本例中第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传入参数给后台任务。
* Progress
* 后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。本例中第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位。
* Result
* 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。本例中第三个泛型参数指定为Boolean,则表示使用布尔型数据来反馈执行结果。
*/
public class ProgressAsyncTask extends AsyncTask<Void,Integer,Boolean> {
// 定义ProgressBar
private ProgressBar pb_test;
// 定义文本控件
private TextView tv_test;
public ProgressAsyncTask(ProgressBar pb_test, TextView tv_test) {
super();
this.pb_test = pb_test;
this.tv_test = tv_test;
}
/**
* 这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,
* 比如显示一个进度条对话框等。
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。
* 任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的
* 第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不
* 可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用
* publishProgress(Progress...)方法来完成。
*/
@Override
protected Boolean doInBackground(Void... voids) {
for (int i = 0; i < 100; i++) {
try {
// 模拟耗时操作
Thread.sleep(100);
publishProgress(i);
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
}
return true;
}
/**
* 当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,
* 方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参
* 数中的数值就可以对界面元素进行相应的更新。
*/
@Override
protected void onProgressUpdate(Integer... values) {
int value = values[0];
pb_test.setProgress(value);
tv_test.setText(value + "");
}
/**
* 当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据
* 会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行
* 的结果,以及关闭掉进度条对话框等。
*/
@Override
protected void onPostExecute(Boolean aBoolean) {
tv_test.setText("进度条加载完毕!");
}
}
我们首先来看一下AsyncTask的基本格式。由于AsyncTask是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们需要为AsyncTask类指定3个泛型参数,这3个参数的用途如下:
Params
:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。Progress
:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。Result
:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
本例中已给出了详尽的注释,读者也可参考注释来配合理解。
在AsyncTask中,比较常用的重写方法如图所示:
在本例中,我们仅重写了AsyncTask中的4个方法,这4个方法相对来说更加重要,它们的作用如下所示:
onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。doInBackground(Params…)
,该方法强制要求重写
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress…)
方法来完成。onProgressUpdate(Progress…)
当在后台任务中调用了publishProgress(Progress…)
方法后,onProgressUpdate (Progress…)
方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
这里为了方便演示,使用了Thread.sleep(100)
模拟耗时操作。
简单来说,使用AsyncTask的步骤就是,在doInBackground()
方法中执行具体的耗时任务, 在onProgressUpdate()
方法中进行UI操作,在onPostExecute()
方法中执行一些任务的收尾工作。
3.4 获取AsyncTask对象并执行任务
当我们创建好AsyncTask的实例后,接下来就是获取其对象,然后调用AsyncTask中的execute()
来执行这个任务。MainActivity的代码如下:
package com.androidframelearn.event_asynctask;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// 定义ProgressBar
private ProgressBar pb_test;
// 定义文本控件
private TextView tv_test;
// 定义按钮
private Button btn_test;
// 定义ProgressAsyncTask
private ProgressAsyncTask mProgressAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化UI
initUI();
// 注册点击事件
btn_test.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mProgressAsyncTask = new ProgressAsyncTask(pb_test, tv_test);
mProgressAsyncTask.execute();
}
});
}
/**
* 初始化UI
*/
private void initUI() {
pb_test = findViewById(R.id.pb_test);
tv_test = findViewById(R.id.tv_test);
btn_test = findViewById(R.id.btn_test);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 关闭AsyncTask,防止内存泄漏
mProgressAsyncTask.cancel(true);
}
}
完成以上两步后,进度条的业务应该就已经实现了。可以运行一下项目,然后查看效果,如图所示:
3.5 注意事项
使用AsyncTask时,有一些问题需要注意:
- 生命周期:AsyncTask不与任何组件绑定生命周期,使用时应该如同本例一样在
Activity
或者Fragment
的onDestroy()
中调用cancel(boolean)
来关闭AsyncTask; - 内存泄漏:若AsyncTask被声明为
Activity
的非静态内部类,当Activity
需销毁时,会因AsyncTask保留对Activity
的引用,而导致Activity无法被回收,最终引起内存泄露,使用时应将AsyncTask声明为Activity
的静态内部类; - 线程执行结果:当
Activity
重新创建时(屏幕旋转 /Activity
被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()
将不生效,即无法更新UI操作,使用时需要在Activity
恢复时的对应方法重启任务线程。