引言:搞 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的使用中查看