Android 异步任务AsyncTask学习

引言:搞 Android 这么久了,一直没有主动去学习使用 AsyncTask ,现在应该很少有人在使用了,但面试中似乎总有人会问。最近一不小心在某项目中看到了相关的代码,就决定对 AsyncTask 进行一番学习。

时间:2016年11月20日11:51:41

作者:JustDo23

邮箱:JustDo_23@163.com

01. 背景

首先,Android 中子线程是不能进行 UI 操作的。单线程操作避免了 UI 混乱的情况。其次,在主线程不能进行耗时操作,否则会阻塞主线程,造成 ANR。因此,将耗时操作放在子线程成为了必然,子线程将处理的结果交给 UI 线程进行 UI 更新,线程间通信必不可少。说到这里,最先想起来的就是 Android 中的 Handler 消息处理机制,除此之外 Android 中还封装了一个抽象类 AsyncTaskAsyncTask其实是对HandlerThread的封装,再者就是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_EXECUTORAsyncTask.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的使用中查看

文章推荐

推荐阅读更多精彩内容