这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战
AsyncTask
对很多老Android来说,是一个很有年代感的东西了,想当年毕业找工作,AsyncTask可是面试必问。
随着 EventBus、RxJava、Kotlin协程 等的出现,它渐渐淡出了我们的视野,面试八股文也少了它的身影,很多新晋的Android开发估计都没听过它。不禁感叹:技术更新迭代真他么快,学不动了!
面试不问,但一些老旧项目中还有用到它,接盘维护难免要学下,索性花一丢丢时间过一下:废弃原因 + 核心原理 + 演进历史。
0x1、Deprecated 原因
AsyncTask,Android(API 3) 引入,一个轻量级的异步任务库,允许以 非线程堵塞 的方式执行操作。经过了好几个版本的调整,比如:
- Android 1.6前,串行执行,原理:一个子线程进行任务的串行执行;
- Android 1.6到2.3,并行执行,原理:一个线程数为5的线程池并行执行,但如果前五个任务执行时间过长,会堵塞后续任务执行,故不适合大量任务并发执行;
- Android 3.0后,串行执行,原理:全局线程池进行串行处理任务;
还有一些小调整,比如Android 10后,线程池核心数变成1,线程队列从BlockingQueue变成SynchronousQueue等。
真够折腾的,打开 AsyncTask的官方文档 一行橙字映入眼帘:
AsyncTask类在 Android 11(API 30) 中被废弃了,推荐使用java.util.concurrent或Kotlin协程来替代。
em...就废弃了啊,往下看是废弃原因的解释:
大概意思:
- AsyncTask本意是使得UI线程的使用变得简单正确,但最常见的用法是集成到UI中,反而导致了 Context泄露、忘记回调、configuration变化Crash 问题。
- 不同版本AsyncTask的兼容问题;
- 吞掉了来自doInBackground的异常;
- 不能提供比直接使用Executor更多的功能;
- Thread和Handler的辅助类,并非线程框架,主要用于执行一些时间不太长的异步任务;
- 用法复杂,三个通用参数(Params、Progress、Result) + 四个回调方法;
简单概括下:版本兼容、功能少、用起来还麻烦。
0x2、AsyncTask 用法详解
Talk is cheap,show you the Code,了解完过时原因,接着温习下它的用法:
// 继承AsyncTask抽象类,建议声明为Activity的静态内部类,避免context泄露
// 泛型参数依次为:
//
// Params → 开始异步任务时传入的参数类型 → execute(Params)
// Progress → 异步任务执行过程,返回进度值
// Result → 异步任务执行完成后,返回结果类型 → doInBackground()
//
class MyAsyncTask: AsyncTask<Task, Int, String>() {
// 必须重写!在此执行耗时操作,返回执行结果
override fun doInBackground(vararg params: Task?): String { return "执行耗时操作" }
// 执行线程任务前的回调,即执行execute()前自动调用,常用作界面初始化操作
override fun onPreExecute() { }
// 在主线程中显示任务执行的进度,自动调用啊,按需重写
override fun onProgressUpdate(vararg values: Int?) { }
// 接受线程任务执行结果,线程任务结束时自动调用,可在此将结果更新到UI组件
override fun onPostExecute(result: String?) { }
// 异步任务被取消时自动调用,用于将异步任务设为取消状态,需在doInBackground()中停止
// 此方法调用onPostExecute就不会被调用
override fun onCancelled() {}
}
// 在主线程中初始化自定义AsyncTask实例后,调用execute(XXX)
// 每个AsyncTask实例只能调用一次execute(),多次调用会抛出异常
// 注①:Activity重建(如屏幕旋转),之前持有的Activity引用已失效,任务正常运行,
// 但任务执行完成后,在onPostExecute中修改UI不会生效,建议在Activity恢复的相关方法
// 中重启AsyncTask任务;
// 注②:最好在Activity、Fragment的onDestory()中调用AsyncTask.cancle()将AsyncTask设置
// 为canceled状态,然后在doInBackground中去判断这个AsyncTask的status是否为:
// AsyncTask.Status.RUNNING,是,直接返回,结束任务
}
em...用法看着也不算太复杂 (不和rx、协程等对比的话),接着以Android 9.0源码为例,讲解一波核心原理~
0x3、核心原理讲解
① 单个任务的流转
跟 execute()
→ executeOnExecutor()
:
AsyncTask内部定义了一个状态字段 mStatus
,可选值有:PENDING(挂起)、RUNNING(运行中)、FINISHED(结束)。
从上述代码也可以看出为何**不能重复调用AsyncTask实例的execute()**方法,接着看下工作线程 → mWorker
:
看完定义,看实现:
往下看是**mFuture
** 的实现:
任务包装类,添加了任务执行完后的回调,调用返回结果的处理方法,跟下:
最终调用的都是 postResult()
方法,利用 Handler
发送了一个标志位 MESSAGE_POST_RESULT
的 Message
。
往下走,跟下自定义Handler → InternalHandler 的具体实现细节:
重写 handleMessage() 对下述两种标记的Message进行处理:
- MESSAGE_POST_RESULT → 任务结束;
- MESSAGE_POST_PROGRESS → 任务进度更新;
任务进度更新那里,回调了 onProgressUpdate()
,在跟下 finish()
:
判断任务取消标记是否为True,是回调 onCancelled()
,否则回调 onPostExecute()
,最后将AsyncTask状态字段设置为**FINISHED
**。
② 多个任务的调度
跟下线程池 Executor
,发现类中定义了 两个静态线程池
(实例共享),先看 THREAD_POOL_EXECUTOR
:
线城池配置:
核心池数(最少2,最多4)最大线程数(CPU数*2+1),非核心线程空闲存活时间(30s)、堵塞队列(LinkedBlockingQueue 堵塞链表队列,上限128,超出会抛异常)
再看另一个线程池 SERIAL_EXECUTOR
:
实现Executor接口,定义了一个 Runnable队列,在 初始化后 和 一个任务执行结束后,都会从队列中获取 任务,并通过 THREAD_POOL_EXECUTOR
线程池执行。
线程池执行任务,而是把任务丢给另一个线程池完成,这是弄啥呢?
em...其实就是保证任务的 串行执行 (队列加同步锁),不想这样玩也也可以,开头这样写道:
调用下 executeOnExecutor(THREAD_POOL_EXECUTOR)
直接用 THREAD_POOL_EXECUTOR
线程池处理任务,就变成 并行执行
了。
以上就是Android 9.0中AsyncTask的实现原理,两个静态线程池,一个串行拿任务,丢到另一个线程池执行,非常简单。接着过下其他版本的AsyncTask的演进历史~
0x4、演进历史
Android 1.6
详细源码:core/java/android/os/AsyncTask.java
改动如下:
// ① 只用到一个线程池,并行执行任务,配置如下
// 核心线程池→5、线程数最大值→128,非核心线程空闲存活时长→10s
// 堵塞队列:LinkedBlockingQueue → 最大值10
// ② 消息类型有三种:
// MESSAGE_POST_RESULT、MESSAGE_POST_PROGRESS、MESSAGE_POST_CANCEL
// ③ 总结:5个核心线程 + 123个非核心线程 + 10个任务 → 最多138个任务,牛啊
// handler还需处理AsyncTask取消的消息,后续变成了一个标志位~
Android 3.2
详细源码:core/java/android/os/AsyncTask.java
改动如下:
// ① 用到了两个线程池,串行执行任务,配置如下:
// 核心线程池-5、线程数最大值-128,非核心线程空闲存活时长-1s
// 堵塞队列:LinkedBlockingQueue → 最大值10
// ② 串行线程池同9.0
// ③ 消息类型少了取消,通过判断mFuture.isCancelled()来判断取消状态
Android 4.4
详细源码:core/java/android/os/AsyncTask.java
改动如下:
// ① 同样是串行,配置不再粗暴写死,而是基于当前可用的处理器数量进行计算:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
// ② 堵塞队列上限变成128
// ③ 提供了串行变并行的setDefaultExecutor(),但被注解为@hide
Android 7.0
详细源码:core/java/android/os/AsyncTask.java
改动如下:
// 动了下线程池配置,看注释
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
// ① 核心线程池改成了2到4之间,不再是粗暴的CPU_COUNT+1,避免极端情况下占用
// 所有CPU影响到其他后台线程的调度~
// ② 线程空闲时间也调整为30s
// ③ 调用allowCoreThreadTimeOut(true),空闲时间过长连核心线程也会回收
Android 8.0
详细源码:core/java/android/os/AsyncTask.java
改动如下:
// 线程池没动,多了两个AsyncTask的构造方法,而且用了@hide注解
public AsyncTask(@Nullable Handler handler) {
this(handler != null ? handler.getLooper() : null);
}
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
}
// 这里猜测是为了扩展,支持外部传入一个Handler,做一些定制化处理,hide说明是想给系统自用的~
Android 10.0
上面的原理基于9.0讲解的,直接跳过讲10.0,详细源码:core/java/android/os/AsyncTask.java
改动如下:
// ① 线程池策略,发生了变化:
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int BACKUP_POOL_SIZE = 5;
private static final int KEEP_ALIVE_SECONDS = 3;
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;
}
// ② 核心线程数→1、最大线程数→20、空闲线程存活时间→3s、堵塞队列变成了SynchronousQueue
// 无缓冲等待队列(不存储元素的堵塞队列,添加元素后等待其他线程去走后才能继续添加)
// ③ 没有采用ThreadPoolExecutor已有的拒绝策略,自定义了一种拒绝策略:
private static final RejectedExecutionHandler sRunOnSerialPolicy =
new RejectedExecutionHandler() {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
android.util.Log.w(LOG_TAG, "Exceeded ThreadPoolExecutor pool size");
// As a last ditch fallback, run it on an executor with an unbounded queue.
// Create this executor lazily, hopefully almost never.
synchronized (this) {
if (sBackupExecutor == null) {
sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>();
sBackupExecutor = new ThreadPoolExecutor(
BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS,
TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory);
sBackupExecutor.allowCoreThreadTimeOut(true);
}
}
sBackupExecutor.execute(r);
}
};
// 定义了一个核心池和最大核心池都为5的线程池,当拒绝策略处理任务,最多可以另外开5个线程
// 来执行任务,如果还不够的话,会添加到一个几乎无限大的LinkedBlockingQueue队列中
Android 11.0
详细源码:core/java/android/os/AsyncTask.java
改动如下:
// 为AsyncTask 添加 @Deprecated注解,一些描述注释
0x5、小结
有关AsyncTask最后的提交定格在2019.12.19,时代的车轮只会滚滚向前,而我们终将化为一粒尘埃。了解源码可以看到工程师对线程池策略的不断调整,很适合背完Java并发八股文后学习巩固~
参考文献