移动开发|Android 异步加载——AsyncTask详谈

##写在前面 昨天在看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。
###基本流程
明白了上述这些基本定义后,我们来看一下异步消息处理机制的大体流程。二话不说,先上图。


从图中我们可以看出,大体流程如下:
  1. 首先我们需要在主线程中创建一个Handler对象,并且需要重写handleMessage方法;
  2. 其次在子线程中需要与主线程进行通信的时候,就通过Message对象携带一些信息,并通过Handler对象发送出去。
  3. 发送出去后该消息会进入MessageQueue等待处理。
  4. 这个时候Looper就起作用了,它一直循环循环终于监测到队列中有一条待处理的消息,立即取出并分发到handleMessage方法,这样主线程就收到这条消息并开始处理。
关于这个机制我就简单介绍到这,如果读者对这个机制又不懂的地方可以Google相关资料,也有大量优秀博文。文末也有相关博文链接,不再赘述。
【移动开发|Android 异步加载——AsyncTask详谈】##初识AsyncTask
###基本介绍 AsyncTask是一种轻量级的异步任务类,它与Handler一样,可以在后台执行任务。并可以把执行任务的过程及结果返回给主线程。这样主线程就可以做一些更新UI的操作。
它的本质是一个抽象的泛型类,所以我们在使用的时候需要继承这个AsyncTask类:
public abstract class AsyncTask
可以看到,类提供了三个参数:
  • Params:任务启动时需要输入的参数类型
  • Progress:后台任务执行中返回的进度值的类型
  • Result:后台执行任务完成后返回结果的类型
同时,它有四个核心方法,我们依次来看:
  1. onPreExecute():该方法在主线程中执行,通常用户完成一些准备和初始化的操作;
  2. doInBackground(Params... params):在子线程中执行,执行异步任务的关键方法,必须重写。此方法中可以调用publishProgress方法来更新任务进度,而publishProgress会调用onProgressUpdate方法来进行进度更新。注意此方法需要返回值给onPostExecute方法;
  3. onProgressUpdate(Progress...values):在主线程中执行,用于任务进度的更新;
  4. onPostExecute(Result result):在主线程中执行,该方法的参数是doInBackground的返回值。
当然还有其他方法,但是不怎么常用,这里不再介绍(其实我也不了解)。
当你自定义一个CustomTask继承AsyncTask复写这几个方法后,就需要开启任务,开启方式为:
new CustomTask().execute();
当然在这里我没有写参数,在实际开发中要注意填入相应的参数。
###执行顺序
当我们自定义Task并复写这四个核心方法后,来看看这几个方法的执行顺序。测试代码如下:
/** * 自定义异步任务类 */ class CustomTask extends AsyncTask {@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日志:


可以看出执行顺序依次是:
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 {/** * 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); } } } 复制代码

对了,一定要记得添加网络权限,不然没有效果。

###模拟进度条
加载网络图片的时候,我们没有用到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 {@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 {@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基础
Android必学之AsyncTask - caobotao
Android消息机制浅析 - xiasuhuei321
AsyncTask和Handler两种异步方式的实现和区别比较
##项目源码
Github - AsyncTaskDemo - IamXiaRui
个人博客:www.iamxiarui.com 原文链接:http://www.iamxiarui.com/?p=699

    推荐阅读