在Android中实现异步任务机制有两种方式,Handler和AsyncTask。
Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更新,这种方式对于整个过程的控制比较精细,但也是有缺点的,例如代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制,为了简化操作,Android1.5提供了工具类android.os.AsyncTask,它使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务。
AsyncTask的定义
public abstract class AsyncTask<Params, Progress, Result>
三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用java.lang.Void类型代替。不能使用基本数据类型例如int,double等
AsyncTask的使用方法
使用AsyncTask需要自定义一个类继承自AsyncTask,里面有这样几个回调函数
- doInBackground : 必须重写,异步操作中,后台线程需要完成的任务
- onPreExecute : 在上一个函数也就是异步操作开始前调用,通常用来完成一些初始化工作
- onPostExecute : 当异步操作执行完毕后系统调用这个函数,通常用来显示doInBackground函数的结果
- onProgressUpdata:在doInBackground()方法中调动publishProgress()方法更新任务进度的时候,就会调用改方法
- onCancelled : 调用AsyncTask.cancel()方法取消线程,当线程停止掉以后回调这个函数而不回调onPostExecute ()。
上面几个回调方法中,只有doInBackgound()方法运行的新线程中,其他的方法均运行在主线程里面,在这些方法里面进行耗时操作可能会产生ANR
使用AsyncTask的注意事项
- 异步任务的实例必须在UI线程中创建。
- execute(Params... params)方法(执行一个异步线程)必须在UI线程中调用。
- 不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。
- 不能在doInBackground(Params... params)中更改UI组件的信息。因为doInBackground运行在新线程
- 一个任务实例只能执行一次,如果执行第二次将会抛出异常。因为线程的生命周期,消亡后不能重生,Java中线程
- 调用AsyncTask.cancel()方法并不会直接停止线程,而是等待当前线程运行完毕去回调onCancelled()方法而不调用onPostExecute ()。
上面两行红色字体是由线程的特性决定的,线程的生命周期是不能回头的,线程不能被强行结束,一般的做法是设置标志位,然后在run方法中判断标志位,Thread.interrupted()并不能中断线程。
使用AsyncTask的demo
我们先来看看效果图,这是加载一张网络上的图片
Demo下载:360云盘下载 访问密码 3b6c 环境为:Android Stdio

布局文件非常简单,两个Button以及一个ProgressBar和一个ImageView,默认的情况下ProgressBar和ImageView是不可见的。
<?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"
android:orientation="vertical">
<Button
android:id="@+id/ioadimg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="加载图片" />
<Button
android:id="@+id/stopload"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停止加载" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone" />
<ImageView
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_launcher"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
然后就是我们继承自AsyncTask的类,MyAsyncTask extends AsyncTask<String, Void, Bitmap>,三个泛型参数前面已经说过了,一个是传入的参数,这是为String类型的,是一个url,第二个为执行的进度,没有使用到,使用Void,第三个是Bitmap,是在doInBackground中加载的网络图片。
class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {
/**
* 在任务提交之前被调用,可以用来执行一些初始化任务
* 运行再主线程
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
mProgressBar.setVisibility(View.VISIBLE);
}
/**
* 异步任务完成后调用,运行在主线程
*
* @param bitmap
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
mProgressBar.setVisibility(View.GONE);
mImagel.setVisibility(View.VISIBLE);
mImagel.setImageBitmap(bitmap);
}
/**
* 执行异步任务,运行在新线程
*/
@Override
protected Bitmap doInBackground(String... params) {
String str = params[0];
Bitmap bitmap = null;
try {
URL url = new URL(str);
InputStream inputStream = url.openStream();
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//下面注释的代码是用来测试cancel是否能真的停止线程
/*long timeMillis = System.currentTimeMillis();
while(System.currentTimeMillis() - timeMillis <= 1000 * 10)
{
}*/
return bitmap;
}
@Override
protected void onCancelled() {
Log.v("x", "取消了");
super.onCancelled();
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
}
下面这段代码是给加载按钮添加点击监听消息,点击的时候加载网络图片
mLoadimg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myAsyncTask = new MyAsyncTask();
myAsyncTask.execute("http://27house.cn/wp-content/uploads/2016/01/android.jpg", "");
}
});
测试AsyncTask.cancel()方法
关于这个方法,并不是真的取消了线程的执行,而是给正在执行的线程发送了一个中断消息,也就是Thread.interrupted();
还是上面那个demo,取消MyAsyncTask中的注释,也就是在doInBackground()返回的时候让这个函数停止10秒。
//下面注释的代码是用来测试cancel是否能真的停止线程
long timeMillis = System.currentTimeMillis();
while (System.currentTimeMillis() - timeMillis <= 1000 * 10) {
}
然后我们点击取消加载按钮,执行AsyncTask.cancel()方法。
mStopLoad.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.v("x", "点击了取消");
myAsyncTask.cancel(true);
}
});
在logcat中打印如下语句,我们可以很清楚的看到,并不是调用cancel()方法以后就直接停止了,而是继续等待后台线程的完成。
02-02 03:21:23.630 29914-30002/com.jay.myasynctask D/dalvikvm: GC_FOR_ALLOC freed 184K, 7% free 3543K/3780K, paused 3ms, total 3ms
02-02 03:21:24.294 29914-29914/com.jay.myasynctask V/x: 点击了取消
02-02 03:21:33.750 29914-29914/com.jay.myasynctask V/x: 取消了
AsyncTask提交的任务的执行顺序
AsyncTask默认的执行的顺序是按照任务提交的顺序,除非调用AsyncTask.executeOnExecutor才是并发执行,不过此方法可能造成数据混乱。下面一个demo是验证这一点的。(源码类似,为了篇幅,可以自行下载)
Demo下载:360云盘下载 访问密码 cc43 环境为:Android Stdio

如图,当点击同时开始的时候,会同时启动两个AsyncTask去更新ProgressBar,我们可以很清楚的看到,只有第一个在更新,只有当第一个任务被取消或者完成以后第二个进度条才会开始更新,这就验证我上面说的,AsyncTask默认是按照任务提交的顺序来执行的。你也可以采用这个系统提供的线程池来处理你的任务AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),默认这个线程池是并发处理任务的,也就是不按顺序来.核心为5条,最大128条,不过这种方法使用不当可能照成数据异常,比如在这个demo中,我们使用AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),当多次点击开始按钮,也就是启动多个任务同时执行去刷新进度,这样当每一个任务进度不同的时候,进度条的显示就会异常。
AsyncTask适合处理短时间的操作,长时间的操作,比如下载一个很大的视频,这就需要你使用自己的线程来下载,不管是断点下载还是其它的.从google官方文档你也可以看到,AsyncTasks should ideally be used for short operations (a few seconds at the most.)
参考文章: liuhe688 AsyncTask执行顺序