看一看过时的→AsyncTask

a306622d5f78cc19aa6af0041c33c559.jpeg

/   今日科技快讯   /

近日,马斯克在推特发文称,将推特平台“蓝V认证”服务的重新启用推迟至11月29日。据了解,马斯克接手推特以来,启动大规模改革,于11月5日正式推出全新订阅服务,每月收费8美元,以向用户提供“蓝V认证”标记。但因仿冒者在平台上制造混乱,这一付费服务于11日被叫停。

/   作者简介   /

本篇文章转自coder_pig的博客,文章主要分享了AsyncTask废弃的原因及演进历史,相信会对大家有所帮助!

原文地址:

https://juejin.cn/post/7035467927356702750

/   前言   /

AsyncTask 对很多老Android来说,是一个很有年代感的东西了,想当年毕业找工作,AsyncTask可是面试必问。

随着 EventBus、RxJava、Kotlin 协程等的出现,它渐渐淡出了我们的视野,面试八股文也少了它的身影,很多新晋的 Android 开发估计都没听过它。不禁感叹:技术更新迭代真他么快,学不动了!

面试不问,但一些老旧项目中还有用到它,接盘维护难免要学下,索性花一丢丢时间过一下:废弃原因 + 核心原理 + 演进历史。

/   Deprecated原因   /

AsyncTask,Android(API 3)引入,一个轻量级的异步任务库,允许以 非线程堵塞 的方式执行操作。经过了好几个版本的调整,比如:

  • Android 1.6前,串行执行,原理:一个子线程进行任务的串行执行;

  • Android 1.6到2.3,并行执行,原理:一个线程数为5的线程池并行执行,但如果前五个任务执行时间过长,会堵塞后续任务执行,故不适合大量任务并发执行;

  • Android 3.0后,串行执行,原理:全局线程池进行串行处理任务;

还有一些小调整,比如Android 10后,线程池核心数变成1,线程队列从 BlockingQueue 变成 SynchronousQueue 等。

真够折腾的,打开 AsyncTask 的官方文档一行橙字映入眼帘:

b1158d638bfcce953366bc1720b3a8ba.png

AsyncTask类在 Android 11(API 30) 中被废弃了,推荐使用java.util.concurrent或Kotlin协程来替代。

em...就废弃了啊,往下看是废弃原因的解释:

4e20407da37912a213ecfced43c7ffe5.png

大概意思:

AsyncTask本意是使得UI线程的使用变得简单正确,但最常见的用法是集成

到UI中,反而导致了 Context泄露、忘记回调、configuration变化Crash

问题。

  • 不同版本AsyncTask的兼容问题;

  • 吞掉了来自doInBackground的异常;

  • 不能提供比直接使用Executor更多的功能;

  • Thread和Handler的辅助类,并非线程框架,主要用于执行一些时间不太长的异步

    任务;

  • 用法复杂,三个通用参数(Params、Progress、Result) + 四个回调方法;

简单概括下:版本兼容、功能少、用起来还麻烦。

/   用法详解   /

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源码为例,讲解一波核心原理~

/   核心原理详解   /

① 单个任务的流转

跟 execute() → executeOnExecutor():

b23bfb5a5abdd25148fa91cd6bc85f2a.png

AsyncTask 内部定义了一个状态字段 mStatus,可选值有:PENDING(挂起)、RUNNING(运行中)、FINISHED(结束)。

从上述代码也可以看出为何不能重复调用 AsyncTask 实例的 execute() 方法,接着看下工作线程 →  mWorker :

e5f5178743478e1d53d99fcf8d095bb5.png

看完定义,看实现:

afac1a076cf13ddf458e1412644d45d9.png

往下看是 mFuture 的实现:

d0525be8c95797c6f48e96b077057171.png

任务包装类,添加了任务执行完后的回调,调用返回结果的处理方法,跟下:

568fca6d17884cfcb63ef840cf9e1ec9.png

最终调用的都是 postResult() 方法,利用 Handler 发送了一个标志位 MESSAGE_POST_RESULT 的 Message。

往下走,跟下自定义Handler → InternalHandler 的具体实现细节:

d446a70172aceb37ce98c8e016a48bca.png

重写 handleMessage() 对下述两种标记的 Message 进行处理:

  • MESSAGE_POST_RESULT → 任务结束;

  • MESSAGE_POST_PROGRESS → 任务进度更新;

任务进度更新那里,回调了 onProgressUpdate(),在跟下 finish():

b5b3e7b934c3083843e42f2dadd79f00.png

判断任务取消标记是否为 True,是回调 onCancelled(),否则回调 onPostExecute(),最后将 AsyncTask 状态字段设置为 FINISHED。

② 多个任务的调度

跟下线程池 Executor,发现类中定义了两个静态线程池(实例共享),先看 THREAD_POOL_EXECUTOR:

f2928eccbe04fda94b0ca60d70656715.png

线城池配置:

核心池数(最少2,最多4)最大线程数(CPU数*2+1),非核心线程空闲存活时间(30s)、堵塞队列(LinkedBlockingQueue 堵塞链表队列,上限128,超出会抛异常)

再看另一个线程池 SERIAL_EXECUTOR:

38a9b67cf158b47cb7055dddfc124f22.png

实现 Executor 接口,定义了一个 Runnable 队列,在初始化后和一个任务执行结束后,都会从队列中获取任务,并通过 THREAD_POOL_EXECUTOR 线程池执行。

线程池执行任务,而是把任务丢给另一个线程池完成,这是弄啥呢?

c4815f9610cf28723fd3e1ca302fe32b.png

em...其实就是保证任务的串行执行(队列加同步锁),不想这样玩也也可以,开头这样写道:

74e7ac702412174ea90191e545c3d5d8.png

调用下 executeOnExecutor(THREAD_POOL_EXECUTOR) 直接用 THREAD_POOL_EXECUTOR 线程池处理任务,就变成并行执行了。

以上就是 Android 9.0 中 AsyncTask 的实现原理,两个静态线程池,一个串行拿任务,丢到另一个线程池执行,非常简单。接着过下其他版本的 AsyncTask 的演进历史~

/   演进历史   /

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注解,一些描述注释

/   总结   /

有关 AsyncTask 最后的提交定格在2019.12.19,时代的车轮只会滚滚向前,而我们终将化为一粒尘埃。了解源码可以看到工程师对线程池策略的不断调整,很适合背完 Java 并发八股文后学习巩固~

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

Kotlin Flow响应式编程,基础知识入门

Android开发中关于内存的那些事,一篇全搞懂

欢迎关注我的公众号

学习技术或投稿

333a54f63123457a7db3af26cfa1065c.png

9bbead591bc3e18d2693ea77d6da4a36.jpeg

长按上图,识别图中二维码即可关注

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值