看完这篇,再也不怕被问到 AsyncTask 的原理了
本文很多资料基于Google Developer官方对AsyncTask的最新介绍。
AsyncTask 是什么

AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.
上文翻译:AsyncTask 是一个被设计为围绕Thread和Handler操作的工具帮助类,而不是作为通用的线程框架,理想情况下,应将AsyncTasks用于短操作(最多几秒钟)。如果需要长时间保持线程运行,Google建议使用java.util.concurrent这个并发包提供的各种API,例如 Executor,ThreadPoolExecutor和 FutureTask。
!This class was deprecated in API level 30.
Use the standard java.util.concurrent or Kotlin concurrency utilities instead.
目前官方已经明确说明,AsyncTask 将会在API 30,也就是Android 11的版本中,将这个类废弃掉。使用java.util.concurrent和Kotlin的协程组件代替AsyncTask 。
谷歌要将AsyncTask废弃掉的原因,我猜测是:AsyncTask 是一个很古老的类了,在API Level 3的时候就有了,还有着许多致命的缺点,终于连Google都忍不了,加上目前已经有许多替代的工具了,如Kotlin协程等。
AsyncTask的缺陷
However, the most common use case was for integrating into UI, and that would cause Context leaks, missed callbacks, or crashes on configuration changes. It also has inconsistent behavior on different versions of the platform, swallows exceptions from doInBackground, and does not provide much utility over using Executors directly.
译:
- AsyncTask 最常见是子类继承然后直接用在 UI层的代码里,这样容易导致Context的内存泄漏问题
- Callback回调的失效
- 配置改变时的崩溃
- 不同版本行为不一致:最初的AsyncTasks在单个后台线程上串行执行;Android1.6时它更改为线程池,允许多个任务并行运行;Android 3.2时又改为只会有单个线程在执行请求,以避免并行执行引起的常见应用程序错误。
- 在它的重要方法doInBackground中会将出现的异常直接吞掉
- 多个实例调用execute后,不能保证异步任务的执行修改顺序合理
- 在直接使用Executors方面没有提供太多实用性
缺点真的蛮多的,简单用用可能还行,但是要考虑清楚
AsyncTask的参数和重要方法
定义子类时需设置传入的三大参数类型<Params , Progress , Result>,如果某类型未用到的将它设为Void
Params(在执行AsyncTask时需要传入的参数,可用于在后台任务中使用)
Progress(后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位)
Result(当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型)
定义子类时可重写四大方法:onPreExecute,onProgressUpdate,doInBackground,onPostExecute
onPreExecute()
这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
doInBackground(Params...)
子线程中运行此方法,可以将几秒的耗时任务在这里运行。任务一旦完成就可以通过return语句来将任务的执行结果进行返回。若Result类型,指定了Void,就不会返回任务执行结果。如果想要更新当前的任务进度想在UI中显示出来,可以通过 publishProgress(Progress...)。
onProgressUpdate(Progress...)
当
doInBackground(params)调用了publishProgress(Progress...)方法后,此方法中会被调用,传递的参数值就是在后台任务中传递过来的,类型是上面说到的Progress的类型。这个方法是在UI线程操作,对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,返回的数据会作为参数传递到此方法中,可以利用返回的
Result来进行一些UI操作。,
AsyncTask开始简单的异步任务
简单来说:AsyncTask是一个抽象类,必须被子类化才能使用,这个子类一般都会覆盖这两个方法doInBackground(Params...)和 onPostExecute(Result),创建AsyncTask子类的实例执行execute方法就运行异步任务了。
//最简单的AsyncTask实现方式
public class DownloadTask extends AsyncTask<String, Integer, Boolean> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Boolean doInBackground(String... strings) {
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Boolean aBoolean) {
super.onPostExecute(aBoolean);
}
}
//在UI线程中启用 AsyncTask
new DownloadTask().execute();
使用AsyncTask要遵守的规矩
必须在UI线程上加载AsyncTask类。
必须在UI线程上创建 AsyncTask子类的实例。
必须在UI线程上调用 execute(Params ...)。
不要手动调用onPreExecute,onPostExecute,doInBackground,onProgressUpdate这几个方法
该任务只能执行一次(如果尝试第二次执行,则将引发异常。)
好鸡肋的设定啊,不知道当初为什么要这样设计
AsyncTask源码分析
先由一行最简单的启动AsyncTask的代码入手:
new DownloadTask().execute("");
进入execute方法查看:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
先看下sDefaultExecutor这个属性是什么名堂:
//sDefaultExecutor 被 volatile修饰
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
//串行执行任务线程池实例
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static class SerialExecutor implements Executor {
//维护一个Runnable的队列
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
//往队尾中压入一个Runnable的同时运行队头的Runnable,维护队列的大小
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
//弹出,执行队头的Runnable
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
//创建核心线程数为 1,最大线程容量为20的线程池
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
可以看到的是sDefaultExecutor,是一个只可以串行执行单个任务的线程池,被调用execute时会将新的Runnable压入任务队列,如果Runnable mActive == null的话会取出队头的Runnable执行,而每当一个任务结束后都会执行任务队列中队头Runnable。
这样做的目的是:保证在不同情况,只能有一个任务可以被执行,SerialExecutor做出了单一线程池的效果。每当一个任务执行完毕后,下一个任务才会得到执行,假如有这么一种情况,主线程快速地启动了很多AsyncTask任务,同一时刻只会有一个线程正在执行,其余的均处于等待状态。
再来看看executeOnExecutor这个方法又有什么名堂:
@MainThread
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();
mWorker.mParams = params;
//由上文提到的 sDefaultExecutor 执行 FutureTask<Result>任务
exec.execute(mFuture);
return this;
}
//Callable的子类,泛型类型为<Params, Result>
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
executeOnExecutor 执行方法,会先检查是否运行了这个任务或者已结束,由于AsyncTask任务规定每个任务只能执行一次,不符合就会抛出异常。接着开始调用 onPreExecute 开始预执行,然后给mWorker赋值参数,执行
mFuture蕴含的任务。到这里好像就没了?非也非也,还有很关键的AsyncTask的构造函数
/**
* 创建一个新的异步任务。 必须在UI线程上调用此构造函数。
*/
public AsyncTask(@Nullable Looper callbackLooper) {
//获取主线程Looper的Handler对象
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
//Worker线程被运行后会执行异步的任务
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//执行doInBackground(mParams)返回 Result类型
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
//FutureTask--泛型参数为 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);
}
}
};
}
//postResultIfNotInvoked 如果未被调用,传递结果
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
//利用Handler将带有result的消息压入消息队列,等待主线程的Handler执行
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
//getHandler()获取到的就是AsyncTask构建对象时创建的‘mHandler’,持有主线程Looper
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
AsyncTask的构造方法,对三个重要的东西(mHandler,mWorker,mFuture)进行了初始化,确保他们在被运行时能够执行相应的任务。
再来看下 mHandler 是 InternalHandler的实例,这个持有主线程Looper的Handler子类的特别之处:
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
//result.mTask属性是AsyncTask的引用
//当接收到不同类型消息,会分别执行 在UI线程更新进度 和 结束任务的方法
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的实例被创建时,获取的是主线程的Looper,所以它的 handleMessage(msg) 方法自然也是在主线程中执行
再看下这个AsyncTask类内的 finish 方法
private void finish(Result result) {
if (isCancelled()) {
//任务已经被取消了,执行onCancelled
onCancelled(result);
} else {
//任务正常结束了,执行onPostExecute(result)
onPostExecute(result);
}
//将任务状态变更为 已结束
mStatus = Status.FINISHED;
}
判断任务是由于被取消掉而结束还是正常结束,执行onCancelled(result) or 执行onPostExecute(result)
源码分析总结
AsyncTask类维护着一个串行执行任务的线程池sDefaultExecutor,一个进程中的所有的 AsyncTask 子类全部在这个线程池中执行。
AysncTask 中的两个线程池(SerialExecutor 和 THREAD_POOL_EXECUTOR)和维护着一个持有主线程Looper的 Handler(InternalHandler),其中线程池 SerialExecutor 保证在不同情况,只能有一个任务可以被执行,THREAD_POOL_EXECUTOR 是核心线程数为1的线程池,负责执行耗时任务,InternalHandler用于将执行环境从后台工作线程切换到主线程,完成UI更新进度,取消任务和任务结束后的UI操作。
看完这篇,再也不怕被问到 AsyncTask 的原理了的更多相关文章
- 看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!
引言 在 android 开发过程中,我们经常需要对一些手势,如:单击.双击.长按.滑动.缩放等,进行监测.这时也就引出了手势监测的概念,所谓的手势监测,说白了就是对于 GestureDetector ...
- 看完这篇Redis缓存三大问题,保你面试能造火箭,工作能拧螺丝。
前言 日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题. 一旦涉及大数据量的需求,如一些商品抢购的情景,或者主页访问量瞬间较 ...
- APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了
APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了 彻底理解android中的内部存储与外部存储 存储在内部还是外部 所有的Android设备均有两个文件存储区域:"intern ...
- 关于 Docker 镜像的操作,看完这篇就够啦 !(下)
紧接着上篇<关于 Docker 镜像的操作,看完这篇就够啦 !(上)>,奉上下篇 !!! 镜像作为 Docker 三大核心概念中最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌 ...
- 【最短路径Floyd算法详解推导过程】看完这篇,你还能不懂Floyd算法?还不会?
简介 Floyd-Warshall算法(Floyd-Warshall algorithm),是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似.该算法名称以 ...
- [转帖]看完这篇文章你还敢说你懂JVM吗?
看完这篇文章你还敢说你懂JVM吗? 在一些物理内存为8g的服务器上,主要运行一个Java服务,系统内存分配如下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约 600m,Linux自身使用 ...
- [转帖]看完这篇文章,我奶奶都懂了https的原理
看完这篇文章,我奶奶都懂了https的原理 http://www.17coding.info/article/22 非对称算法 以及 CA证书 公钥 核心是 大的质数不一分解 还有 就是 椭圆曲线算法 ...
- Mysql快速入门(看完这篇能够满足80%的日常开发)
这是一篇mysql的学习笔记,整理结合了网上搜索的教程以及自己看的视频教程,看完这篇能够满足80%的日常开发了. 菜鸟教程:https://www.runoob.com/mysql/mysql-tut ...
- 看完这篇 Linux 权限后,通透了!
我们在使用 Linux 的过程中,或多或少都会遇到一些关于使用者和群组的问题,比如最常见的你想要在某个路径下执行某个指令,会经常出现这个错误提示 . permission denied 反正我大概率见 ...
随机推荐
- windows挂载nas存储
操作系统:windows server 2016 1.安装nfs客户端打开程序面板 2.点击下一步 3.点击下一步 4.下一步 5.这里只选择文件和存储服务器就可以 6.选择nfs客户端,安装 7.m ...
- 一个ACE 架构的 C++ Timer
.h #ifndef _Timer_Task_ #define _Timer_Task_ #pragma once #include <ace/Task.h> #include <a ...
- C#客户端通过安全凭证调用webservice
怎么解决给XML Web services 客户端加上安全凭据,从而实现调用安全的远程web方法?首先,有远程web服务Service继承自System.Web.Services.Protocols. ...
- java语言进阶(七)_Lambda表达式
1 函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方案,也就是"拿什么东西做什么事情".相对而言,面向对象过分强调"必须通过对象的形式来做事情" ...
- POJ 3463 Sightseeing 题解
题目 Tour operator Your Personal Holiday organises guided bus trips across the Benelux. Every day the ...
- UVA11300 Spreading the Wealth 题解
题目 A Communist regime is trying to redistribute wealth in a village. They have have decided to sit e ...
- BUUCTF-BJD(更新V1.0)
CTF-Day1 (PS:第一次写博客,就是想记录自己的一一步一步) Misc: 问卷调查 | SOLVED |题最简单的misc-y1ng | SOLVED |Real_EasyBaBa | SOL ...
- STL测试2)计算器简单实现
实现一个基本的计算器来计算一个简单的字符串表达式的值. 字符串表达式可以包含左括号 ( ,右括号 ),加号 + ,减号 -,非负整数和空格 . 示例 1: 输入: "1 + 1" ...
- JavaScript的参数是按照什么方式传递的?
1.基本类型传递方式 <script> var a = 1; function test(x) { x = 10; console.log(x); } test(a); // consol ...
- Mysql基础(三):MySQL基础数据类型、完整性约束、sql_mode模式
目录 2.MySQL基础数据类型.完整性约束.sql_mode模式 1. MySQL常用数据类型 2. 完整性约束 3. MySQL的sql_mode模式说明以及设置 2.MySQL基础数据类型.完整 ...