一、概述

Android开发中我们通常让主线程负责前台用户界面的绘制以及响应用户的操作,让工作者线程在后台执行一些比较耗时的任务。Android中的工作者线程主要有AsyncTask、IntentService、HandlerThread,它们本质上都是对线程或线程池的封装。

AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类(轻量体现在使用方便、代码简洁),它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。

AsyncTask的内部封装了两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler)。

其中SerialExecutor线程池用于任务的排队,让需要执行的多个耗时任务,按顺序排列,THREAD_POOL_EXECUTOR线程池才真正地执行任务,InternalHandler用于从工作线程切换到主线程。

二、AsyncTask的使用

1.AsyncTask类

AsyncTask的类声明如下:

public abstract class AsyncTask<Params, Progress, Result> 

可见AsyncTask是一个泛型抽象类

三个参数:

  • Params:开始异步任务执行时传入的参数类型;
  • Progress:异步任务执行过程中,返回下载进度值的类型;
  • Result:异步任务执行完成后,返回的结果类型;

2.四个主要方法

(1)onPreExecute()

 /**
* Runs on the UI thread before {@link #doInBackground}.
*
*/
@MainThread
protected void onPreExecute() {
}

这个方法会在后台任务开始执行之间调用,在主线程执行。用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

(2) doInBackground(Params...params)

 /**
* Override this method to perform a computation on a background thread. The
* specified parameters are the parameters passed to {@link #execute}
* by the caller of this task.
*
* This method can call {@link #publishProgress} to publish updates
* on the UI thread.
*
* @param params The parameters of the task.
*
* @return A result, defined by the subclass of this task.
*
*/
@WorkerThread
protected abstract Result doInBackground(Params... params);

这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务

任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。

(3)onProgressUpdate(Prgress...values)

/**
* Runs on the UI thread after {@link #publishProgress} is invoked.
* The specified values are the values passed to {@link #publishProgress}.
*
* @param values The values indicating progress.
*
*/
@MainThread
protected void onProgressUpdate(Progress... values) {
}

当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,在主线程中进行,利用参数中的数值就可以对界面元素进行相应的更新。

(4)onPostExecute(Result)

 /**
* <p>Runs on the UI thread after {@link #doInBackground}. The
* specified result is the value returned by {@link #doInBackground}.</p>
*
* <p>This method won't be invoked if the task was cancelled.</p>
*
* @param result The result of the operation computed by {@link #doInBackground}.
*
*/
@MainThread
protected void onPostExecute(Result result) {
}

doInBackground(Params...)执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,在主线程中进行,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

上面几个方法的调用顺序: onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()

如果不需要执行更新进度则为onPreExecute() --> doInBackground() --> onPostExecute(),

除了上面四个方法,AsyncTask还提供了onCancelled()方法,它同样在主线程中执行,当异步任务取消时,onCancelled()会被调用,这个时候onPostExecute()则不会被调用,但是要注意的是,AsyncTask中的cancel()方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。就好比想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。

3.使用AsyncTask的注意事项

①异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。

②execute(Params... params)方法必须在UI线程中调用。

③不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。

④不能在doInBackground(Params... params)中更改UI组件的信息。

⑤一个任务实例只能执行一次,如果执行第二次将会抛出异常。

三、AsyncTask原理

(1)构造函数

/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
}; mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}

初始化了两个变量,mWorker和mFuture,并在初始化mFuture的时候将mWorker作为参数传入。mWorker是一个Callable对象,mFuture是一个FutureTask对象,这两个变量会暂时保存在内存中,稍后才会用到它们。 FutureTask实现了Runnable接口。

mWorker中的call()方法执行了耗时操作,即result = doInBackground(mParams);,然后把执行得到的结果通过postResult(result);,传递给内部的Handler跳转到主线程中。在这里这是实例化了两个变量,并没有开启执行任务。

(2)execute方法

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}

execute方法调用了executeOnExecutor方法并传递参数sDefaultExecutor和params。
再看executeOnExecutor方法:

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) { //判断当前状态
if (mStatus != Status.PENDING) {
switch (mStatus) {
case 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)");
}
} //将状态置为运行态
mStatus = Status.RUNNING; //主线程中最先调用onPreExecute方法,进行准备工作
onPreExecute(); //将参数传给mWorker
mWorker.mParams = params; //调用线程池,执行任务
exec.execute(mFuture); return this;
}

executeOnExecutor方法首先判断状态,若处于可执行态,则将状态置为RUNNING。然后调用了onPreExecute方法,交给用户进行执行任务前的准备工作。核心部分在于 exec.execute(mFuture)。exec即sDefaultExecutor。

(3) sDefaultExecutor串行线程池

查看sDefaultExecutor定义:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

再看一下SerialExecutor类

private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive; public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
} protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}

sDefaultExecutor是一个串行线程池,作用在于任务的排队执行。

从SerialExecutor的源码可以看出,mFuture是插入到mTasks任务队列的对象。当mTasks中没有正在活动的AsyncTask任务,则调用scheduleNext方法执行下一个任务。若一个AsyncTask任务执行完毕,则继续执行下一个AsyncTask任务,直至所有任务执行完毕。通过分析可以发现真正去执行后台任务的是线程池THREAD_POOL_EXECUTOR。

在这个方法中,有两个主要步骤。 ①向队列中加入一个新的任务,即之前实例化后的mFuture对象。

②调用 scheduleNext()方法,调用THREAD_POOL_EXECUTOR执行队列头部的任务。

(4) 线程池THREAD_POOL_EXECUTOR

THREAD_POOL_EXECUTOR定义如下:

/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR; static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}

实际是个线程池,开启了一定数量的核心线程和工作线程。然后调用线程池的execute()方法。执行具体的耗时任务,即开头构造函数中mWorker中call()方法的内容。先执行完doInBackground()方法,又执行postResult()方法,下面看该方法的具体内容:

(5) postResult

private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}

该方法向Handler对象发送了一个消息,下面具体看AsyncTask中实例化的Hanlder对象的源码

(6)InternalHandler

private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
} @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}

InternalHandler接收到MESSAGE_POST_RESULT时调用result.mTask.finish(result.mData[0]);即执行完了doInBackground()方法并传递结果,那么就调用finish()方法。

 private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}

finish方法中若任务没有取消则调用onPostExecute方法发送结果,若任务取消则调用onCancelled方法。finish方法是在主线程中执行的。

InternalHandler是一个静态类,为了能够将执行环境切换到主线程,因此这个类必须在主线程中进行加载。所以变相要求AsyncTask的类必须在主线程中进行加载。

(7) onProgressUpdate

通过上述流程已经顺序找到了onPreExecutedoInBackgroundonPostExecute方法,那么onProgressUpdate是如何执行的呢?
首先查看 publishProgress方法:

protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
doInBackground中调用publishProgress方法,publishProgress方法发送MESSAGE_POST_PROGRESS消息和进度values,InternalHandler在接收到MESSAGE_POST_PROGRESS消息中调用onProgressUpdate方法。因此onProgressUpdate也是在主线程中调用。

(8) 小结

通过上述一步步的源码分析过程,已经掌握了AsyncTask任务的执行过程。AsyncTask中有两个线程池串行线程池sDefaultExecutor和线程池THREAD_POOL_EXECUTOR。sDefaultExecutor用于任务的排队,THREAD_POOL_EXECUTOR真正的执行任务。线程的切换使用Handler(InternalHandler)实现。

 

AsyncTask的串行和并行

从上述源码分析中分析得到,默认情况下AsyncTask的执行效果是串行的,因为有了SerialExecutor类来维持保证队列的串行。如果想使用并行执行任务,那么可以直接跳过SerialExecutor类,使用executeOnExecutor()来执行任务。

三、常见问题

1)为什么AsyncTask在主线程创建执行?
因为AsyncTask需要在主线程创建InternalHandler,以便onProgressUpdateonPostExecuteonCancelled 可以正常更新UI。
2)为什么AsyncTask不适合特别耗时任务?
AsyncTask实际上是一个线程池。如果有线程长时间占用,且没有空闲,则其他线程只能处于等待状态,会造成阻塞。
3)AsyncTask内存泄漏问题
如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

四、AsyncTask使用不当的后果

1.)生命周期

AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);

2.)内存泄漏

如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

3.) 结果丢失

屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

参考https://www.jianshu.com/p/679d3819533a

https://github.com/LRH1993/android_interview/blob/master/android/basis/asynctask.md

AsyncTask原理的更多相关文章

  1. AsyncTask介绍

    AsyncTask介绍 AsyncTask比Handler更轻量级一些,适用于简单的异步处理. 使用AsyncTask时,注意重写以下几个方法: 1. doInBackground() 作用:执行后台 ...

  2. 2019 Android 高级面试题总结 从java语言到AIDL使用与原理

    说下你所知道的设计模式与使用场景 a.建造者模式: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 使用场景比如最常见的AlertDialog,拿我们开发过程中举例,比如C ...

  3. Android复习资料

    转载:http://blog.csdn.net/huachao1001/article/details/53156582 在10月份开始就没有再参与校招了,面试过程真的很累,有时现场等面试一等就是几个 ...

  4. Android研发进阶之路

    前言 移动研发火热不停,越来越多人开始学习android开发.但很多人感觉入门容易成长很难,对未来比较迷茫,不知道自己技能该怎么提升,到达下一阶段需要补充哪些内容.市面上也多是谈论知识图谱,缺少体系和 ...

  5. 2019 Android 高级面试题总结

    说下你所知道的设计模式与使用场景 a.建造者模式: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 使用场景比如最常见的AlertDialog,拿我们开发过程中举例,比如C ...

  6. BATJ等大厂最全经典面试题分享

    金九银十,又到了面试求职高峰期,最近有很多网友都在求大厂面试题.正好我之前电脑里面有这方面的整理,于是就发上来分享给大家. 这些题目是网友去百度.蚂蚁金服.小米.乐视.美团.58.猎豹.360.新浪. ...

  7. 一些面试基本知识(Android篇一)

    Android Activity设计模式之MVC模式(介绍MVP之前的引子) 參考博客.文章 http://www.cnblogs.com/liqw/p/4175325.html MVC即Model- ...

  8. android 知识小结-1

    Java哪些数据结构是线程安全的,CurrentHashMap的原理 ConcurrentHashMap.ConcurrentSkipListMap.ConcurrentSkipListSet.Con ...

  9. Android高级工程师面试题整理

    这些题目是网友去百度.小米.乐视.美团.58.猎豹.360.新浪.搜狐等一线互联网公司面试被问到的题目.熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率. 主要分为以下几部分: (1)java ...

随机推荐

  1. Postman 使用详解

    转自: http://blog.csdn.net/flowerspring/article/details/52774399 Postman 使用详解 阅读 6754收藏 3022016-6-9 作者 ...

  2. java基础学习周计划之3--每周一练

    每周一练第一周 一. 关键代码:1.斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...数列第一项和第二项是1, 从第三项开始, ...

  3. SpringBoot(二)_项目属性配置

    修改端口 在main/resources/application.properties修改端口 server.port=8088 此时启动访问localhost:8088/hello 就会看到 Hel ...

  4. 阿里云Centos7.x MySql安装教程示例

    创建用户 useradd mysql; passwd mysql; 下载(比如:5.5.61) 地址 https://dev.mysql.com/downloads/mysql/5.6.html#do ...

  5. 安卓开发笔记(二十八):仿写IOS switch选择器控件实现,checkbox

    我们先来看看效果: 这里我们主要使用了github上的一个开源项目,配置起来比较方便,下面解释一下该如何使用:首先是:Gradle文件当中进行配置: dependencies { implementa ...

  6. 自学Python,新手上路,好资源免费分享

    Python 可以用来做什么? 在我看来,基本上可以不负责任地认为,Python 可以做任何事情.无论是从入门级选手到专业级选手都在做的爬虫,还是Web 程序开发.桌面程序开发还是科学计算.图像处理, ...

  7. API 测试的具体实现

    目录 API 测试的具体实现 基于 Spring Boot 构建的 API 使用 cURL 命令行工具进行测试 使用图形界面工具 Postman 进行测试 如何应对复杂场景的 API 测试? 总结 A ...

  8. asp.net core系列 46 Identity介绍

    一. Identity 介绍 ASP.NET Core Identity是一个会员系统,可为ASP.NET Core应用程序添加登录功能.可以使用SQL Server数据库配置身份以存储用户名,密码和 ...

  9. link/Extended dependency 无法显示连接

    把矩形控件先去掉,然后就能看到表与表之间的 link/Extended dependency 连线了.

  10. JS原型--原型链

    构造函数-->原型--->prototype-->__proto__-->constructor-->原型链 构造函数    什么是构造函数?我理解构造函数就是可以用来生 ...