写在前面
昨天在看Android中的缓存机制,顺带把异步加载任务复习了一遍,尤其是AsyncTask。由于我在一般情况下使用较多的就是Handler方式执行异步任务,很少用到AsyncTask,所以现在总结一下加深自己的印象,并分享出来与大家交流。后几天会发一些关于Android中的缓存机制。
在本文书写过程中,我借鉴的资料有《第一行代码》、《Android开发艺术探索》、慕课网的一期课程以及相关的博文,链接在文末注明。下面是本文的目录,比较简单。
- 异步消息处理机制
- 组成部分
- 基本流程
- 初识AsyncTask
- 基本介绍
- 执行顺序
- 注意事项
- AsyncTask的使用
- 加载网络图片
- 模拟进度条
- AsyncTask和Handler两种异步方式区别
- 资料来源
- 项目源码
异步消息处理机制
异步消息处理机制是Android开发中的基础知识。为了方便后面的AsyncTask的理解,我就先简单说一下这个机制,但不作为本文的重点。
组成部分
Android中的异步消息主要是由四个部分组成:
- Message:消息
- MessageQueue:消息队列
- Handler:消息处理者
- Looper:消息循环者
下面我们依次来看:
1、Message
Message就是异步消息中需要传递的消息。它会在内部携带一些少量的信息,用于不同线程之间交换数据。它本身有四个字段,用来处理不同类型的信息:
- what:消息的一些标志,可以根据不同的标志做不同的处理
- obj:字段为Object类型,这个不用多解释了,对象中的大Boss
- arg1和arg2:整型类型,可以存放一些标志位或不易混淆的整型值
2、MessageQueue
知道了Message就很好理解这个了,这是一个消息队列。队列中存放着大量的Message,这些Message都是没有被处理的,所以放在队列中等待处理。值得注意的是,每个线程中有且仅有一个MessageQueue对象。
3、Handler
这个我们很熟悉,不好翻译,一般用来处理消息。我们就叫它消息处理者吧。它是处理机制中一个比较重要的角色,是用来发送和处理消息的。主要是两个方法:
- sendMessage(Message msg):发送消息
- handleMessage(Message msg):处理消息
也就是说经过Handler发送的消息最终会传递到handleMessage中,进而被处理。
4、Looper
Looper不是很好理解。《第一行代码》中将其看作MessageQueue的管家,比较抽象。而《Android开发艺术探索》中将其定义成消息循环者,这个比较符合逻辑。也就是说,当线程中的Looper调用自身的loop()方法后,它会进入到一个无限循环中,循环监测MessageQueue中是否存在消息,如有存在一条未处理的消息,Looper就会将其取出,传递给Handler的handleMessage方法,让其处理。
这里我们需要注意两点:
- 每个线程中有且仅有一个Looper对象。
- 新new出来的Thread是没有Looper的,也就是只有UI线程默认有Looper。
基本流程
明白了上述这些基本定义后,我们来看一下异步消息处理机制的大体流程。二话不说,先上图。
异步消息处理流程图从图中我们可以看出,大体流程如下:
- 首先我们需要在主线程中创建一个Handler对象,并且需要重写handleMessage方法;
- 其次在子线程中需要与主线程进行通信的时候,就通过Message对象携带一些信息,并通过Handler对象发送出去。
- 发送出去后该消息会进入MessageQueue等待处理。
- 这个时候Looper就起作用了,它一直循环循环终于监测到队列中有一条待处理的消息,立即取出并分发到handleMessage方法,这样主线程就收到这条消息并开始处理。
关于这个机制我就简单介绍到这,如果读者对这个机制又不懂的地方可以Google相关资料,也有大量优秀博文。文末也有相关博文链接,不再赘述。
初识AsyncTask
基本介绍
AsyncTask是一种轻量级的异步任务类,它与Handler一样,可以在后台执行任务。并可以把执行任务的过程及结果返回给主线程。这样主线程就可以做一些更新UI的操作。
它的本质是一个抽象的泛型类,所以我们在使用的时候需要继承这个AsyncTask类:
public abstract class AsyncTask<Params,Progress,Result>
可以看到,类提供了三个参数:
- Params:任务启动时需要输入的参数类型
- Progress:后台任务执行中返回的进度值的类型
- Result:后台执行任务完成后返回结果的类型
同时,它有四个核心方法,我们依次来看:
- onPreExecute():该方法在主线程中执行,通常用户完成一些准备和初始化的操作;
- doInBackground(Params... params):在子线程中执行,执行异步任务的关键方法,必须重写。此方法中可以调用publishProgress方法来更新任务进度,而publishProgress会调用onProgressUpdate方法来进行进度更新。注意此方法需要返回值给onPostExecute方法;
- onProgressUpdate(Progress...values):在主线程中执行,用于任务进度的更新;
- onPostExecute(Result result):在主线程中执行,该方法的参数是doInBackground的返回值。
当然还有其他方法,但是不怎么常用,这里不再介绍(其实我也不了解)。
当你自定义一个CustomTask继承AsyncTask复写这几个方法后,就需要开启任务,开启方式为:
new CustomTask().execute();
当然在这里我没有写参数,在实际开发中要注意填入相应的参数。
执行顺序
当我们自定义Task并复写这四个核心方法后,来看看这几个方法的执行顺序。测试代码如下:
/**
* 自定义异步任务类
*/
class CustomTask extends AsyncTask<Void, Void, Void> {
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.w("Task", " ----> onPreExecute");
}
@Override
protected Void doInBackground(Void... params) {
publishProgress();
Log.w("Task", " ----> doInBackground");
return null;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
Log.w("Task", " ----> onProgressUpdate");
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Log.w("Task", " ----> onPostExecute");
}
}
来看Log日志:
执行顺序Log日志可以看出执行顺序依次是:
onPreExecute -> doInBackground -> onProgressUpdate -> onPostExecute
注意事项
基本了解了AsyncTask后,我们需要知道一些注意事项和使用过程中的一些条件限制。
- 在四个核心方法中,只有doInBackground方法运行在子线程,其他方法都运行在主线程;
- AsyncTask类必须在主线程中加载,也就是第一次访问AsyncTask必须发生在主线程;
- AsyncTask对象必须在主线程中创建;
- AsyncTask的execute方法必须在主线程中调用;
- 四个核心方法只能系统自动调用,而不能主动调用;
AsyncTask的使用
好了,看到这里相信你已经对AsyncTask有了一个基本的认识了,现在我们就通过两个实例在看AsyncTask在项目中的具体使用。
加载网络图片
首先来看第一个Demo,通过图片的URL地址加载网络图片。已经是网络图片了,当然是耗时任务,所以我们需要开启异步任务。当然可以开启子线程来获取,但是这里我们采取AsyncTask这样的方式。完成效果如下:
加载网络图片来看代码,代码比较简单,注释也写的很清晰,就不再解释了:
/**
* 异步任务加载网络图片
*/
public class NetPicActivity extends AppCompatActivity {
private ImageView netPicImage;
private ProgressBar netPicProgressBar;
private static String picUrl = "http://www.iamxiarui.com/wp-content/uploads/2016/05/壁纸.jpg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_net_pic);
netPicImage = (ImageView) findViewById(R.id.iv_netpic);
netPicProgressBar = (ProgressBar) findViewById(R.id.pb_netpic);
//通过调用execute方法开始处理异步任务.相当于线程中的start方法.
new NetAsyncTask().execute(picUrl);
}
/**
* 自定义网络请求异步任务
*/
class NetAsyncTask extends AsyncTask<String, Void, Bitmap> {
/**
* onPreExecute用于异步处理前的操作
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
//此处将progressBar设置为可见.
netPicProgressBar.setVisibility(View.VISIBLE);
}
/**
* 在doInBackground方法中进行异步任务的处理
*
* @param params 参数为URL
* @return Bitmap对象
*/
@Override
protected Bitmap doInBackground(String... params) {
//获取传进来的参数
String url = params[0];
Bitmap bitmap = null;
URLConnection connection;
InputStream is;
try {
connection = new URL(url).openConnection();
is = connection.getInputStream();
//为了更清楚的看到加载图片的等待操作,将线程休眠3秒钟
Thread.sleep(3000);
BufferedInputStream bis = new BufferedInputStream(is);
//通过decodeStream方法解析输入流
bitmap = BitmapFactory.decodeStream(bis);
is.close();
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
/**
* onPostExecute用于UI的更新.此方法的参数为doInBackground方法返回的值
*
* @param bitmap 网络图片
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//隐藏progressBar
netPicProgressBar.setVisibility(View.GONE);
//更新imageView
netPicImage.setImageBitmap(bitmap);
}
}
}
对了,一定要记得添加网络权限,不然没有效果。
<uses-permission android:name="android.permission.INTERNET"/>
模拟进度条
加载网络图片的时候,我们没有用到onProgressUpdate方法,现在就模拟一个进度条加载的Demo,效果如下:
模拟进度条来看代码,这里我们模拟了进度条的加载,并在doInBackground中调用了publishProgress方法,让进度条更新,具体代码如下:
/**
* 用异步任务模拟进度条的更新
*/
public class ProgressActivity extends AppCompatActivity {
private ProgressBar mainProgressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progress);
mainProgressBar = (ProgressBar) findViewById(R.id.pb_main);
//开启异步任务
new PbAsyncTask().execute();
}
/**
* 自定义异步任务类
*/
class PbAsyncTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
//使用for循环来模拟进度条的进度.
for (int i = 0; i < 100; i++) {
//调用publishProgress方法将自动触发onProgressUpdate方法来进行进度条的更新.
publishProgress(i);
try {
//通过线程休眠模拟耗时操作
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
//通过publishProgress方法传过来的值进行进度条的更新.
mainProgressBar.setProgress(values[0]);
}
}
}
注意,这里我们来看下面的图,我们可以发现当第一次进度条没有全部完成时,返回重新点击开启进度条。此时进度条并没有开始运行,而是等待一段时间后,才开始更新进度。
进度条异常为什么呢?因为AsyncTask是基于线程池进行实现的,当一个线程没有结束时,后面的线程是不能执行的。也就是说必须等到第一个task的for循环结束后,才能执行第二个task。
那么如何解决呢?我们知道,当我们点击BACK键时会调用Activity的onPause()方法,所以我们可以在Activity的onPause()方法中将正在执行的task标记为cancel状态,在doInBackground方法中进行异步处理时判断是否是cancel状态来决定是否取消之前的task。
更改后的代码如下:
/**
* 用异步任务模拟进度条的更新
*/
public class ProgressActivity extends AppCompatActivity {
private ProgressBar mainProgressBar;
private PbAsyncTask pbAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progress);
mainProgressBar = (ProgressBar) findViewById(R.id.pb_main);
//开启异步任务
pbAsyncTask = new PbAsyncTask();
pbAsyncTask.execute();
}
@Override
protected void onPause() {
super.onPause();
if (pbAsyncTask != null && pbAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
//cancel方法只是将对应的AsyncTask标记为cancelt状态,并不是真正的取消线程的执行.
pbAsyncTask.cancel(true);
}
}
/**
* 自定义异步任务类
*/
class PbAsyncTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
//使用for循环来模拟进度条的进度.
for (int i = 0; i < 100; i++) {
//如果task是cancel状态,则终止for循环,以进行下个task的执行.
if (isCancelled()) {
break;
}
//调用publishProgress方法将自动触发onProgressUpdate方法来进行进度条的更新.
publishProgress(i);
try {
//通过线程休眠模拟耗时操作
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
//通过publishProgress方法传过来的值进行进度条的更新.
mainProgressBar.setProgress(values[0]);
}
}
}
效果如下:
进度条异常解决已经完美解决了这个问题,在这里我们要注意,cancel方法只是将对应的AsyncTask标记为cancel状态,并不是真正的取消线程的执行,想要真正取消线程,还是需要在doInBackground方法中停止。
AsyncTask和Handler两种异步方式区别
已经看完两个Demo了,应该对AsyncTask有了很深的理解了。但是想到这里不得不提出一个疑问,这看起来不就是开启一个子线程嘛,有啥不同的。其实AsyncTask和Handler两种异步方式还真有很大的区别。
首先来看AsyncTask,它是Android提供的轻量级的异步类,可以直接继承AsyncTask。在类中实现异步操作,并提供接口来反馈当前异步执行的程度,也就是所谓的进度更新,最后反馈执行的结果给UI主线程。这种方法使用起来简单快捷,过程清晰明了而且便于控制。不足的是在在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来,代码也看起来比较臃肿。
其次是Handler,在本文开头的时候,就说了异步消息处理机制。它是通过Handler, Looper, Message,Thread四个对象之间的联系来进行处理消息的。这种方式在功能上比较清晰,有多个后台任务的时候代码看起来比较有序。
所以说在实际开发过程中,根据需要来选择异步任务处理方式,就我个人而言,还是Handler方式用的比较多。
好了,本文基本技术了。由于我技术水平有限,如有错误或不同意见,欢迎指正与交流。
优秀资料来源
Android必学之AsyncTask - caobotao
AsyncTask和Handler两种异步方式的实现和区别比较
项目源码
Github - AsyncTaskDemo - IamXiaRui
网友评论