AsyncTask:异步任务
在前面的章节有提到过,Android 系统默认会在主线程(UI 线程)执行任务,但是如果有耗时程序就会阻塞 UI 线程,导致页面卡顿。这时候我们通常会将耗时任务放在独立的线程,然后通过 Handler 等线程间通信机制完成 UI 的刷新。很多时候我们也许只是想执行一个简单的任务,为此写一套 Handler 线程通信就会显得比较复杂,不用担心,Android 系统为我们提供了一个专门用于执行异步任务的工具——Async Task,它可以让我们执行后台任务并轻松的与 UI 线程进行状态同步,今天就一起来学习一下 AyncTask 的用法。
1. AsyncTask 简介
AsyncTask 类通常用来在执行后台任务的同时刷新 UI,通过调用execute()
方法触发后台任务的执行,首先会回调 AsyncTask 的onPreExecute()
,接着回调doInBackground()
来执行自定义的后台任务,最后回调onPostExecute()
方法用来刷新 UI,整体流程示意图如下:
2. AsyncTask 的基本用法
2.1 声明 AsyncTask
我们不能直接创建 AsyncTask,正确的方法是继承自 AsyncTask 实现一个它的子类,如下:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
// 执行后台耗时任务
return;
}
protected void onProgressUpdate(Integer... progress) {
// 任务执行进度更新
}
protected void onPostExecute(Long result) {
// 执行完毕,更新UI
}
}
以上是 AsyncTask 的核心回调方法,每个方法的含义会在后面具体讲到。
2.2 指定参数
AsyncTask 可以帮助你在子线程和主线程之间同步参数,根据不同的业务场景,参数类型和个数也会不一样。刚刚在 2.1 小节声明 AyncTask 子类的时候,需要传入 3 个泛型参数:
-
TypeOfVarArgParams:
在任务启动之后,传入给后台任务的参数类型 -
ProgressValue:
启动之后到任务结束之间,系统会不断回调此方法,用来更新任务的进度 -
** ResultValue:**
后台任务的执行结果
2.3 启动后台任务
在声明完后台任务之后,就可以直接启动了。启动方式比较简单,直接通过调用execute()
方法启动后台任务:
new DownloadFilesTask().execute(url1, url2, url3);
3 AsyncTask 关键回调方法
AsyncTask 是由 4 个回调方法配合组成,这 4 个回调方法按照一定的顺序依次被调用,所以我们需要 focus 的是在正确的回调方法中实现我们想要的逻辑,下面详细看看如何使用。
- onPreExecute():
在执行execute()
方法之后该方法立即被调用,标志着 AyncTask 正式开启。通常用来做一些需要在后台任务开启之前完成的初始化工作,比如展示一个进度条、或者弹出一个对话框等等。该方法在 UI 线程执行。 - doInBackground(Params):
在执行完onPreExecute()
方法之后立即被调用,用来执行需要放在后台执行的耗时任务。在创建 AsyncTask 的时候传入的参数就是提供给doInBackground
使用的。在后台任务执行完毕后,还需要将执行结果返回到onPostExecutes ()
中,同时我们也可以通过publishProgress(Progress…)
方法来手动发布任务进度,进度将从子线程发送到 UI 线程。毋庸置疑,该方法在子线程中执行。 - onProgressUpdate(Progress…):
当我们通过publishProgress(Params)
发布进度之后,系统会回调该方法,用来获取任务执行进度并更新 UI。这一步就完成了子线程到主线程的通信,该方法在 UI 线程执行 - onPostExecute(Result):
当后台任务执行完毕,该方法被回调,同时标志着整个 AyncTask 结束。与onPreExecute
相反,通常会在onPostExecute
中做一些回收工作,比如提示“下载完成”、“加载失败”、隐藏进度条等等。
4 AsyncTask 示例
本节的功能和第 38 节 Handler 的功能类似,我们还是完成一个后台执行耗时任务,并同步更新进度条的功能,可以直接在 Handler 的例子上修改。
4.1 布局文件
布局文件基本一样,只是默认将进度条和进度显示隐藏起来,等到onPreExecute()
方法的时候在做初始化展示,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:visibility="gone"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
<Button
android:id="@+id/start_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/progressBar"
android:layout_alignParentStart="true"
android:layout_marginStart="24dp"
android:layout_marginTop="62dp"
android:text="开始任务" />
<TextView
android:visibility="gone"
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/start_progress"
android:layout_alignBottom="@+id/start_progress"
android:layout_alignParentEnd="true"
android:layout_marginEnd="85dp"
android:gravity="center"
android:text="当前进度:0%"
android:textSize="16sp" />
</RelativeLayout>
4.2 异步任务控制
异步任务的代码基本上和 Handler 一致,我们通过Thread.sleep(1000)
来模拟一秒钟的耗时操作,然后在onPreExecute()
中展示进度条,在后台任务执行完后通过publishProgress(int)
来将任务进度发送到主线程,由onProgressUpdate(int)
方法在 UI 线程接收进度,并更新进度条。最后在结束的时候返回“已完成”提示,在onPostExecute(String)
方法中进行收尾工作,隐藏进度条并展示“已完成”。代码如下:
package com.emercy.myapplication;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final int MAX = 100;
private ProgressBar progressBar;
private Button startProgress;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progressBar);
startProgress = findViewById(R.id.start_progress);
textView = findViewById(R.id.textView);
progressBar.setMax(MAX);
startProgress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new DownloadTask().execute();
}
});
}
// 1、创建Async Task子类
private class DownloadTask extends AsyncTask<Integer, Integer, String> {
// 2、初始化阶段,展示进度条
@Override
protected void onPreExecute() {
progressBar.setVisibility(View.VISIBLE);
textView.setVisibility(View.VISIBLE);
}
// 3、执行后台任务
@Override
protected String doInBackground(Integer... integers) {
int i;
for (i = 0; i < 100; i++) {
try {
// 一秒钟的耗时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 4、发布进度
publishProgress(i);
}
return "异步任务已完成";
}
// 5、接收后台任务数据并更新进度条
@Override
protected void onProgressUpdate(Integer... values) {
textView.setText("当前进度:" + values[0] + "%");
progressBar.setProgress(values[0]);
}
// 6、任务结束
@Override
protected void onPostExecute(String s) {
progressBar.setVisibility(View.GONE);
textView.setText(s);
}
}
}
编译之后,效果如下:
可以看到大体的效果和 Handler 是一样的,但是整个异步任务的处理流程更加清晰了,而且可以很方便的做任务前的初始化及任务后的回收工作,整个线程的切换也不再需要我们去控制,全部交给 AsyncTask 操作就行,一切就是这么简单!
5 小结
本节介绍了一个专门用于执行异步任务的工具类,首先需要创建一个子类继承自 AsyncTask,然后确定好需要传入和返回的参数类型,接着覆写 4 个关键的方法,剩下的线程切换及数据传输就交给系统完成了。
相比 Handler,AsyncTask使用更简单,我们只需要关注我们的后台逻辑,而 Handler 需要我们自行管理的东西比较多。所以在执行后台任务刷新 UI 的场景,强烈推荐使用 AsyncTask,可以让我们的开发更加顺畅。