#yyds干货盘点#源码康一康过时的→AsyncTask

卧疾丰暇豫,翰墨时间作。这篇文章主要讲述#yyds干货盘点#源码康一康过时的→AsyncTask相关的知识,希望能为你提供帮助。
AsyncTask 对很多老android来说,是一个很有年代感的东西了,想当年毕业找工作,AsyncTask可是面试必问。
随着 EventBus、Rxjava、Kotlin协程 等的出现,它渐渐淡出了我们的视野,面试八股文也少了它的身影,很多新晋的Android开发估计都没听过它。不禁感叹:技术更新迭代真他么快,学不动了!

#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

面试不问,但一些老旧项目中还有用到它,接盘维护难免要学下,索性花一丢丢时间过一下:废弃原因 + 核心原理 + 演进历史。
0x1、Deprecated 原因AsyncTask,Android(API 3) 引入,一个轻量级的异步任务库,允许以 非线程堵塞 的方式执行操作。经过了好几个版本的调整,比如:
  • Android 1.6前,串行执行,原理:一个子线程进行任务的串行执行;
  • Android 1.6到2.3,并行执行,原理:一个线程数为5的线程池并行执行,但如果前五个任务执行时间过长,会堵塞后续任务执行,故不适合大量任务并发执行;
  • Android 3.0后,串行执行,原理:全局线程池进行串行处理任务;
还有一些小调整,比如Android 10后,线程池核心数变成1,线程队列从BlockingQueue变成SynchronousQueue等。
真够折腾的,打开 AsyncTask的官方文档 一行橙字映入眼帘:
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

em...就废弃了啊,往下看是废弃原因的解释:
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

大概意思:
简单概括下:版本兼容、功能少、用起来还麻烦。
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?): Stringreturn "执行耗时操作" // 执行线程任务前的回调,即执行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()
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

AsyncTask内部定义了一个状态字段 mStatus,可选值有:PENDING(挂起)、RUNNING(运行中)、FINISHED(结束)。
从上述代码也可以看出为何不能重复调用AsyncTask实例的execute()方法,接着看下工作线程 →mWorker
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

看完定义,看实现:
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

往下看是mFuture 的实现:
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

任务包装类,添加了任务执行完后的回调,调用返回结果的处理方法,跟下:
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

最终调用的都是 postResult() 方法,利用 Handler 发送了一个标志位 MESSAGE_POST_RESULTMessage
往下走,跟下自定义Handler → InternalHandler 的具体实现细节:
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

重写 handleMessage() 对下述两种标记的Message进行处理:
  • MESSAGE_POST_RESULT → 任务结束;
  • MESSAGE_POST_PROGRESS → 任务进度更新;
任务进度更新那里,回调了 onProgressUpdate(),在跟下 finish()
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

判断任务取消标记是否为True,是回调 onCancelled(),否则回调 onPostExecute(),最后将AsyncTask状态字段设置为FINISHED
② 多个任务的调度
跟下线程池 Executor,发现类中定义了 两个静态线程池 (实例共享),先看 THREAD_POOL_EXECUTOR
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

线城池配置:
再看另一个线程池 SERIAL_EXECUTOR
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

实现Executor接口,定义了一个 Runnable队列,在 初始化后 和 一个任务执行结束后,都会从队列中获取 任务,并通过 THREAD_POOL_EXECUTOR 线程池执行。
线程池执行任务,而是把任务丢给另一个线程池完成,这是弄啥呢?
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

em...其实就是保证任务的 串行执行 (队列加同步锁),不想这样玩也也可以,开头这样写道:
#yyds干货盘点#源码康一康过时的→AsyncTask

文章图片

调用下 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并发八股文后学习巩固~
【#yyds干货盘点#源码康一康过时的→AsyncTask】参考文献
  • 带你轻松看源码---AsyncTask(异步任务)
  • 基于Android10.0代码深入了解终将老去的AsyncTask
  • Android AsyncTask Deprecated, Now What?
  • 说说AsyncTask演化历程里的小纠结

    推荐阅读