引言:搞 Android 这么久了,一直没有主动去学习使用 AsyncTask ,现在应该很少有人在使用了,但面试中似乎总有人会问。最近一不小心在某项目中看到了相关的代码,就决定对 AsyncTask 进行一番学习。
时间:2016年11月20日11:51:41
作者:JustDo23
01. 背景
首先,Android 中子线程是不能进行 UI 操作的。单线程操作避免了 UI 混乱的情况。其次,在主线程不能进行耗时操作,否则会阻塞主线程,造成 ANR。因此,将耗时操作放在子线程成为了必然,子线程将处理的结果交给 UI 线程进行 UI 更新,线程间通信必不可少。说到这里,最先想起来的就是 Android 中的 Handler 消息处理机制,除此之外 Android 中还封装了一个抽象类 AsyncTask。AsyncTask其实是对Handler和Thread的封装,再者就是AsyncTask内部是线程池实现。
在单线程模式中需要始终明确两条:
- UI 线程中能耗时操作,不能被阻塞
- 非 UI 线程不能直接去更新 UI
02. 介绍
官方网站上介绍到AsyncTask是方便的线程操作,允许在后台进行操作并将结果返回给 UI 线程。建议在AsyncTask中执行比较短的操作,如果是灰常灰常耗时的操作则强烈建议使用Executor等线程池进行。一个异步任务定义3个泛型参数Params,Progress,Result,4个步骤onPreExecute,doInBackground,onProgressUpdate,onPostExecute;三个泛型就是启动参数,任务进度,返回结果,四个步骤就是启动准备,后台执行,任务进度,任务结果。
03. 入门代码
新建一个SimpleTask继承AsyncTak,接着需要指定三个泛型,看到报错提示必须重写doInBackground方法。然后继续重写其他的方法。
/**
* 简单使用
*
* @author JustDo23
*/
public class SimpleTask extends AsyncTask<Void, Void, Void> {
/**
* 异步操作执行前初始化操作
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
LogUtil.e("--->onPreExecute()");
}
/**
* 异步执行后台线程将要完成的任务[这个是必须重写的方法]
*
* @param params 泛型中的参数
*/
@Override
protected Void doInBackground(Void... params) {
LogUtil.e("--->doInBackground()");
publishProgress();// 进行进度更新
return null;
}
/**
* 异步任务[doInBackground]完成后系统自动回调
*
* @param result [doInBackground]返回的结果
*/
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
LogUtil.e("--->onPostExecute()");
}
/**
* 进度更新 [doInBackground]方法中调用 publishProgress 方法后执行
*
* @param progress
*/
@Override
protected void onProgressUpdate(Void... progress) {
super.onProgressUpdate(progress);
LogUtil.e("--->onProgressUpdate()");
}
@Override
protected void onCancelled(Void aVoid) {
super.onCancelled(aVoid);
}
@Override
protected void onCancelled() {
super.onCancelled();
}
}
在 Activity 中的onCreate方法中进行调用
new SimpleTask().execute();
打印出执行步骤
E/JustDo23: --->onPreExecute()
E/JustDo23: --->doInBackground()
E/JustDo23: --->onProgressUpdate()
E/JustDo23: --->onPostExecute()
04. 异步加载网络图片
布局代码
<?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"
android:orientation="vertical"
android:padding="20dp">
<ImageView
android:id="@+id/iv_net"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ProgressBar
android:id="@+id/pb_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>
</RelativeLayout>
网络加载代码
/**
* 展示图片
*
* @author JustDo23
*/
public class ImageShowActivity extends Activity {
private ImageView iv_net;// 图片
private ProgressBar pb_loading;// 加载圈
private String imageUrl = "http://img4.duitang.com/uploads/item/201611/03/20161103224830_YGisc.thumb.700_0.jpeg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_show);
iv_net = (ImageView) findViewById(R.id.iv_net);
pb_loading = (ProgressBar) findViewById(R.id.pb_loading);
new ImageTask().execute(imageUrl);// 在这里进行调用
}
class ImageTask extends AsyncTask<String, Integer, Bitmap> {
@Override
protected void onPreExecute() {
super.onPreExecute();
pb_loading.setVisibility(View.VISIBLE);// 先将 loading 显示
}
@Override
protected Bitmap doInBackground(String... params) {
String url = params[0];// 从参数中获取网络地址
Bitmap bitmap = null;// 从网络加载的图片
try {
Thread.sleep(3000);// 以下是进行网络获取图片
URLConnection urlConnection = new URL(url).openConnection();
InputStream inputStream = urlConnection.getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
bitmap = BitmapFactory.decodeStream(bufferedInputStream);
inputStream.close();
bufferedInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;// 将加载的图片返回
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
pb_loading.setVisibility(View.GONE);// 将 loading 隐藏
iv_net.setImageBitmap(bitmap);// 界面上设置显示图片
}
}
}
05. 进度条显示进度
使用水平的进度条和一个 for 循环来模拟一下网络加载进度及界面的更新
/**
* 利用 AsyncTask 实现进度加载显示
*
* @author JustDo23
*/
public class LoadingActivity extends Activity {
private ProgressBar pb_loading;// 加载条
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acitvity_loading);
pb_loading = (ProgressBar) findViewById(R.id.pb_loading);
new LoadingTask().execute();
}
class LoadingTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
try {// 循环模拟加载进度
for (int i = 0; i <= 100; i++) {
LogUtil.e("progress = " + i);
publishProgress(i);// 去更新界面
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... progress) {
pb_loading.setProgress(progress[0]);
}
}
}
频繁关闭启动界面,发现界面上的进度条有时会一直没有进度;在日志打印过程中,发现AsyncTask是从0到100的打印,一圈打印完了再打印第二圈。也就是AsyncTask内部维护的 task 是串行的,一个执行完了,再去执行下一个。
06. 优化
public class LoadingActivity extends Activity {
private ProgressBar pb_loading;// 加载条
private LoadingTask loadingTask;// 异步加载任务
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acitvity_loading);
pb_loading = (ProgressBar) findViewById(R.id.pb_loading);
loadingTask = new LoadingTask();
loadingTask.execute();
}
@Override
protected void onPause() {
super.onPause();
if (loadingTask != null && loadingTask.getStatus() == AsyncTask.Status.RUNNING) {
loadingTask.cancel(true);// cancel 方法只是将对应的 AsyncTask 标记为了取消状态,并不是真的取消线程执行了。
}
}
class LoadingTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
try {// 循环模拟加载进度
for (int i = 0; i <= 100; i++) {
if (isCancelled()) {
break;// 如果是取消状态就直接跳出自动结束线程
}
LogUtil.e("progress = " + i);
publishProgress(i);// 去更新界面
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... progress) {
if (isCancelled()) {
return;// 如果是取消状态就直接返回
}
pb_loading.setProgress(progress[0]);
}
}
}
注意:cancel 方法只是将对应的 AsyncTask 标记为了取消状态,并不是真的取消线程执行了。
到这里,便对AsyncTask有了一个基础的入门。以上的学习主要参考慕课网视频Android必学-AsyncTask基础
07. 串并行
网上有很多关于AsyncTask的串并行问题介绍,起初AsyncTask是串行的,一个一个的执行;接着修改成了并行,多线程并发执行;接着又修改成了支持串行和并行,我们直接调用execute(Params... params)是进行串行,调用executeOnExecutor(Executor exec, Params... params)是并传递类型,来选择进行并行还是串行。其实点开execute源码
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
从中可以看到是同一方法。参数有:AsyncTask.SERIAL_EXECUTOR和AsyncTask.THREAD_POOL_EXECUTOR;第一个是默认的,串行操作;第二个是进行并行操作。
08. 强调
- 异步任务的实例必须在 UI 线程中创建
-
execute(Params... params)方法必须在 UI 线程中调用 - 不要手动回调
onPreExecute(),doInBackground(Params... params),onPostExecute(Result result),onProgressUpdate(Progress... values)这四个方法 - 不能在
doInBackground(Params... params)方法中更新 UI - 一个任务实例只能执行一次,第二次执行就会抛出异常
java.lang.IllegalStateException: Cannot execute task: the task is already running
09. 小综合
参考 CSDN 上的文章详解Android中AsyncTask的使用自己动手写了一个完整的 Demo
- 在执行
AsyncTask.cancel(true);的时候先回调onCancelled()然后再回调onCancelled(Result result)点击看下源码onCancelled(Result result)的内部实现是直接调用onCancelled() - 在执行
AsyncTask.cancel(true);后,onPostExecute(Result result)方法是没有进行回调的 - AsyncTask 的状态被存放在一个枚举
Status中,总共有三种状态FINISHED,PENDING,RUNNING - 点击查看一下
execute(Params... params)方法就会明白为啥只能执行一次
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
//如果该任务正在被执行则抛出异常
//值得一提的是,在调用cancel取消任务后,状态仍未RUNNING
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
//如果该任务已经执行完成则抛出异常
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
//改变状态为RUNNING
mStatus = Status.RUNNING;
//调用onPreExecute方法
onPreExecute();
mWorker.mParams = params;
sExecutor.execute(mFuture);
return this;
}
更多源码解析可以到文章详解Android中AsyncTask的使用中查看