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的使用中查看

文章推荐

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容