在之前的博客中,已经详细地介绍了Android中异步消息处理机制的原理和整个流程,Handler、Message、MessageQueue、Looper各自的作用相信大家都已经很熟悉了。今天介绍的是多线程编程方面又一个重要的角色——AsyncTask。
一、概述
二、AsyncTask基本使用
三、AsyncTask源码
四、拓展
一、AsyncTask是什么
在Handler异步消息处理机制中,通过Handler发送消息处理消息,我们可以在非UI线程里对UI界面进行更新。但是,在实际使用中,因为要操作线程和Handler,过程可能会稍微有点复杂,重复的代码还是比较多的。
AsyncTask,翻译为异步任务,它封装了Thread和Handler,可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。通过AsyncTask,我们可以很方便地完成一些需要异步执行的任务。当然,需要注意的一点是,AsyncTask不适合特别耗时的后台任务,所以对于特别耗时的任务来说,建议使用线程池等。
二、AsyncTask怎么用
简介
AsyncTask是一个抽象类,一般在使用过程中我们会创建一个任务类,继承自AsyncTask,AsyncTask有三个泛型参数,分别是Params, Progress,Result,其中Params是参数的类型,Progress是后台任务执行进度的类型,Result是后台任务返回结果的类型,如果没有具体的类型就用Void代替。
AsyncTask提供了4个重要的方法,分别是onPreExecute(),doInBackground(),onProgressUpdate(),onPostExecute(),其中doInBackground主要是执行耗时操作,onPreExecute主要是做一些准备工作,它是在UI线程里执行的,onProgressUpdate用于进度更新,onPostExecute则是用于发布结果。
主线程里实例化AsyncTask并调用execute方法来启动即可。
实例
下面我们通过一个实例来了解AsyncTask的基本用法,实例主要是通过网络请求获取服务器的数据,并把数据展示在界面上。
/**
* Created by JackalTsc on 2016/7/25.
*/
public class AsyncTaskActivity extends Activity {
private TextView tvData;
private String dataUrl = "http://115.159.149.87:8080/testssm/user/usertest";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_activity_asynctask);
initView();
}
private void initView() {
tvData = (TextView) findViewById(R.id.tv_data);
Button btnStartAsync = (Button) findViewById(R.id.btn_start_async);
btnStartAsync.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//执行异步任务
new taskGetData().execute(dataUrl);
}
});
}
private class taskGetData extends AsyncTask<String, Void, StringBuffer> {
//在方法doInBackground()之前UI线程里调用.
@Override
protected void onPreExecute() {
super.onPreExecute();
tvData.setText("正在获取数据...");
}
//这里是执行耗时操作
@Override
protected StringBuffer doInBackground(String... params) {
StringBuffer result = new StringBuffer("获取到的数据为:");
//使用HttpURLConnection进行网络请求获取数据
HttpURLConnection connection = null;
try {
URL mURL = new URL(params[0]);
connection = (HttpURLConnection) mURL.openConnection();
connection.setConnectTimeout(5000);
if (connection.getResponseCode() == 200) {
InputStream is = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String data;
try {
while ((data = bufferedReader.readLine()) != null) {
result.append(data);
}
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return result;
}
//获取进度更新
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
//doInBackground方法获取到数据后调用
@Override
protected void onPostExecute(StringBuffer result) {
super.onPostExecute(result);
tvData.setText(result);
}
}
}
布局文件很简单,layout_activity_asynctask.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="Medium Text" />
<Button
android:id="@+id/btn_start_async"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取数据" />
</LinearLayout>
在上面的例子中,我们先创建继承自AsyncTask的异步任务类taskGetData,因为是直接根据url来获取数据,这里我们传入参数类型为String,返回类型为StringBuffer,任务类里重写了方法onPreExecute,让TextView先显示文字 “ 正在获取数据... ”,重写doInBackground方法进行网络请求获取数据,返回result,最后result会传递到onPostExecute方法里,我们在这里更新UI界面,显示数据。当然,创建好异步任务类后,我们通过new taskGetData().execute(dataUrl)来执行异步任务,这样就可以了。
三、AsyncTask源码
这部分我们主要是看下AsyncTask的源码,了解AsyncTask的实现原理。这里展示的只是关键的几步代码,要详细了解的话还是自己去看源码比较好。
- 1. execute()方法
当我们传入参数启动异步任务时,调用的是方法execute()。execute方法内部实现是调用executeOnExecutor方法,传入一个串行的线程池sDefaultExecutor和之前我们传入的参数。
- 2. executeOnExecutor()方法
接着看方法executeOnExecutor,先执行onPreExecute(),也就是我们可以重写来在执行前做初始化工作的地方,然后传入参数mFuture给SerialExecutor的execute方法,线程池开始执行。(mFuture是FutureTask实例,FutureTask是个并发类,充当Runnable的作用)
- 3. SerialExecutor的execute方法
查看SerialExecutor的execute方法。可以看到,方法中先是调用offer()方法,把传入的FutureTask实例插入到任务队列中,之后如果没有正在活动的任务,就调用scheduleNext()执行下个任务。同时当一个AsyncTask完成后,AsyncTask会继续执行其它任务直到都执行完。FutureTask的run方法会调用mWorker的call()方法。
- 4. 线程池
AsyncTask中有两个线程池和一个Handler,其中线程池SerialExecutor用于任务排队,线程池THREAD_POOL_EXECUTOR用于任务执行。在AsyncTask的构造方法中,有下面这段代码。可以看到,在mWorker的call()方法中会调用doInBackground方法,之后把得到的结果传递给postResult。
- 5. postResult()
再看postResult()方法,不难发现,这里先获取Handler,然后发送消息。
- 6. postResult()
最后,我们查看AsyncTask内部的Handler,不难发现,接收到消息后,如果是MESSAGE_POST_RESULT标识,那么继续调用onPostExecute,这样整个过程就结束了。
四、拓展
在查看AsyncTask的源码时,我们可以看到其中用到了两个线程池。这里简单地对线程池作个介绍,算是初步接触,以后再作详细学习。
进程是一个“执行中的程序”。通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
线程的引入可以减小开销,但是创建和销毁时难免有开销。引入线程池的好处的优点有三个,一是重用线程池中的线程,可以避免因为线程的创建和销毁而带来的性能开销。二是有效控制线程池的最大并发数,避免大量线程间因互相抢占系统资源而导致的阻塞现象。三是能够对线程进行简单的管理,提供定时执行及指定间隔循环执行的功能。
在AsyncTask类里,我们可以看到其中有创建线程池THREAD_POOL_EXECUTOR: