【Android】深入解析 AsyncTask 源码

前言:AsyncTaskAndroid SDK 中提供的一个用于执行异步任务的框架,在 Android 兴起的早期被广泛使用,但如今在 API 30 被弃用,取而代之的是 java.util.concurrentKotlin 协程。虽然它存在着一些不足,但是我们还是可以尝试了解一下它的实现原理以及存在的不足。

注:本文以 API 29 的 AsyncTask 源码进行解析。

功能概述

首先,让我们来简单地了解一下它的设计初衷:

AsyncTask的设计初衷是能够帮助用户方便地完成异步任务的线程调度,它对用户提供了如下几个接口:

public abstract class AsyncTask<Params, Progress, Result> {
    @WorkerThread
    protected abstract Result doInBackground(Params... params);

    @MainThread
    protected void onPreExecute() {
    }

    @MainThread
    protected void onPostExecute(Result result) {
    }

    @MainThread
    protected void onProgressUpdate(Progress... values) {
    }

    @MainThread
    protected void onCancelled(Result result) {
        onCancelled();
    }
}

首先看到它的三个范型参数:

  • Params:代表传递给它的参数类型
  • Progress:代表用于反馈进度的数据类型
  • Result:代表异步执行的结果类型

用户可以通过实现doInBackground方法来编写在异步线程所要进行的处理;通过onPostExecute方法的重写实现对异步请求结果的获取;通过onPreExecute实现了在doInBackground之前进行一些预处理;通过onProgressUpdate实现对进度的监听;通过onCancelled实现对任务中断的监听。

当我们编写一个AsyncTask后,只需要调用它的execute方法即可,而背后的线程调度的过程都会由它替我们完成,看上去是十分美好的。

同时从上面的代码中可以看到,每个方法都有被@MainThread的注解标注,这些注解主要的作用是用来标注这个方法运行所处的线程。可以看出,只有doInBackground是在异步线程进行执行。


创建

让我们看看它的创建过程,来到它的构造函数:

public AsyncTask() {
    this((Looper) null);
}

它的无参构造函数转调到了它的有参构造函数,这个构造函数需要以Looper作为一个参数:

public AsyncTask(@Nullable Looper callbackLooper) {
    // 根据传递进来的 Looper 来构建了一个 Handler,若没有指定 Looper 或指定的 Looper 是主线程的 Looper,
    // 则指定内置的 InternalHandler 对消息进行处理(见附1),否则构造一个对应的 Handler。
    // 可以看出 AsyncTask 不是一定回到主线程的。
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
           ? getMainHandler()
           : new Handler(callbackLooper);
  
    // 构建了一个 WorkerRunnable,WorkerRunnable 实际上是对 Callable 的简单包装(见附2),
    // 区别仅仅在于可以传入参数。
    mWorker = new WorkerRunnable<Params, Result>() {
        // 当 mWorker 被执行时
        public Result call() throws Exception {
            // 将当前 Task 设置为被执行
            mTaskInvoked.set(true);
            Result result = null;
            try {
                // 进行线程优先级的设置
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                // 调用 doInBackground 方法并获取了返回值
                result = doInBackground(mParams);
                Binder.flushPendingCommands();
            } catch (Throwable tr) {
                mCancelled.set(true);
                throw tr;
            } finally {
                // 看来这个 WorkerRunnable 是在异步线程中执行的。
                // 不论成功与否,它最后都会调用 postResult 进行结果的交付。
                postResult(result);
            }
            // 返回结果
            return(result);
        }
    };

    // 基于前面的 mWorker 构建了一个 FutureTask
    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            try {
                // 当执行完成或被取消时,会调用 postResultIfNotInvoked 方法传入 mWorker 的执行结果。
                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 方法传入 mWorker 的执行结果。
                postResultIfNotInvoked(null);
            }
        }
    };
}
附1
private static Handler getMainHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler(Looper.getMainLooper());
        }
        return(sHandler);
    }
}
附2
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
    Params[] mParams;
}

总的来说,其构造过程主要是对 Handler、WorkerRunnable 以及 FutureTask 进行了构建。


执行

让我们看看当调用execute之后它做了什么:

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

可以看到,它转调到了executeOnExecutor方法,并且传入了一个Executor对象sDefaultExecutor,我们先不去关注这个Executor的设计,让我们先看看executeOnExecutor方法:

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
                                    Params...params) {
    // 首先,它对当前 Task 的状态进行了检查,AsyncTask 共有三种状态(见附1)
    // 只有 PENDING 的 Task 才能被 execute。
    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)");
        }
    }

    // 改变当前 Task 的状态
    mStatus = Status.RUNNING;

    // 调用 onPreExecute 方法进行了用户实现的预处理
    onPreExecute();

    // 将用户的参数交给 mWorker
    mWorker.mParams = params;
    // 将 mFuture 交给了传入的线程池进行处理
    exec.execute(mFuture);

    return(this);
}
附1
public enum Status {
    // 待执行
    PENDING,
    // 正在执行
    RUNNING,
    // 已执行
    FINISHED,
}

FutureTask执行后,会使得mWorker被执行,它执行后会将mTaskInvoked.set(true),并调用postResult进行结果的交付:

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

这里实际上就是把结果包装成了AsyncTaskResult类并放入了消息队列,这样就实现了线程的切换,将消息通过Handler由子线程发送给了主线程(指AsyncTask被指定的线程),当Handler收到消息后,就会对消息进行处理。

回到AsyncTask创建的时候,我们可以看到在构造时没有指定Looper的情况下,默认的InternalHandler是如何进行处理的:

private static Handler getMainHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler(Looper.getMainLooper());
        }
        return(sHandler);
    }
}
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) {
                // 在 MESSAGE_POST_RESULT 消息下,它在收到 AsyncTaskResult 后会调用 finish 方法进行整个任务
                // 的完成处理(见附1)
                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;
        }
    }
}
附1
private void finish(Result result) {
    // 若任务已取消,它会调用 onCancelled 方法进行回调
    if (isCancelled()) {
        onCancelled(result);
    } 
    // 若任务正常完成,它会调用 onPostExecute 方法
    else {
        onPostExecute(result);
    }
  
    // 设置状态为 FINISHED
    mStatus = Status.FINISHED;
}

当 FutureTask 执行完成后,会调用postResultIfNotInvoked方法,并传入mWorker的执行结果:

private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {
        postResult(result);
    }
}

可以看到,这里实际上是在Task没有被Invoke的情况下才会调用到,由于mWorker在被执行时首先就会将wasTaskInvoked置为 true,因此实际上这里很少能被调用到。

我们再看看onProgressUpdated何时会被调用到,我们可以找到MESSAGE_POST_PROGRESS的消息是何时被发出的:

@WorkerThread
protected final void publishProgress(Progress...values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                        new AsyncTaskResult<Progress>(this, values )).sendToTarget();
    }
}

它是在publishProgress方法中被调用的,这个方法是提供给用户的,因此进度的更新需要用户自行在doInBackground中调用publishProgress从而实现。


取消

我们看看任务的取消,看到cancel方法:

public final boolean cancel(boolean mayInterruptIfRunning) {
    mCancelled.set(true);
    // cancel 方法最后会调用到 mFuture 的 cancel,并且它需要传递一个参数 mayInterruptIfRunning,
    // 见附1
    return(mFuture.cancel(mayInterruptIfRunning));
}
附1
public boolean cancel(boolean mayInterruptIfRunning) {
    if (!(state == NEW &&
           U.compareAndSwapInt(this, STATE, NEW,
                    mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return(false);
    try {
            // 当 mayInterruptIfRunning 为 true 时,即使任务在运行时也会被打断。
            // 而如果为 false,则 doInBackground 仍然会一直执行到结束。
            if (mayInterruptIfRunning) {
                    try {
                            Thread t = runner;
                            if (t != null)
                                    t.interrupt();
                    } finally {
                            U.putOrderedInt(this, STATE, INTERRUPTED);
                    }
            }
    } finally {
        finishCompletion();
    }
    return(true);
}

这个设计非常奇怪,按道理来说取消的场景只有任务正在执行时需要取消,完全可以提供一个这个参数默认为 true 的方法供外部调用。并且这个方法只能尽量让任务尽快结束,如果执行的过程中有一些不可打断的操作,则这个方法调用后仍然不会使得任务停止。


线程池

对于AsyncTask,我们还有最后一个问题没有研究,那就是它执行任务的sDefaultExecutor

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

可以看到它的默认值为SERIAL_EXECUTOR

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

SERIAL_EXECUTORSerialExecutor的实例。

private static class SerialExecutor implements Executor {
    // Executor 内部维护了一个 ArrayDeque 队列,这个队列是一个任务缓存队列,
    // 里面存放了对真正要执行的 Runnable 进行包装的 Runnable。
    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();
                      }
        }
    } );

        // 当 mActive 为 null,它会调用 scheduleNext 方法。
        if (mActive == null) {
            scheduleNext();
        }
    }

    // 在 scheduleNext 方法中会从 mTasks 队首取出任务赋值给 mActive,
    // 之后通过 THREAD_POOL_EXECUTOR 这个线程池对该任务进行执行。
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

当任务执行完成后,会继续调用scheduleNext对队列中的下一个任务进行执行。通过这样的设计,Runnable的执行变成了一种按序执行,只有前一个执行结束,才会进行下一个任务的执行,因此AsyncTask的执行顺序实际上是一种串行执行。

那么THREAD_POOL_EXECUTOR又是一个怎样的线程池呢?让我们看看它的声明:

private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
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;
}

可以看到,THREAD_POOL_EXECUTOR的配置如下:

  • CORE_POOL_SIZE:内部核心线程个数
  • MAXIMUM_POOL_SIZE:最大线程数量
  • KEEP_ALIVE_SECONDS:每一个线程空闲后等待的时间,时间到了就会被停止

只保留一个池线程。如果需要,让池增长到相当多的线程,但让他们很快超时。 在不太可能发生线程用尽的情况下,退回到一个简单的无界队列执行程序。

这种组合可确保:

  • 保持少量的线程
  • 仅在启动明显更大,但仍有限的线程集之后才进行排队
  • 将线程总数限制为有界,但仍然允许无限制的任务集排队。

不足之处

AsyncTask看似十分美好,但实际上存在着非常多的不足,这些不足使得它在 API 30被废弃:

  • 生命周期:AsyncTask没有与ActivityFragment的生命周期绑定,即使Activity被销毁,它的doInBackground任务仍然会继续执行。
  • 取消任务:AsyncTaskcancel方法的参数mayInterruptIfRunning存在的意义不大,并且它无法保证任务一定能取消,只能尽快让任务取消(比如,如果正在进行一些无法打断的操作时,任务就仍然会执行)
  • 内存泄漏:由于它没有与Activity等生命周期进行绑定,因此它的生命周期仍然可能比Activity长,如果将它作为Activity的非static内部类,则它会持有Activity的引用,导致Activity的内存无法释放。

总结

本文基本上对AsyncTask的实现原理有一个大致的了解了,它的原理实际上还是挺简单的:通过ExecutorFutureTask配合,从而实现任务的异步执行,最后在任务结束后通过Handler进行线程的切换从而实现了整个线程调度的功能。在Android 3.0之后,默认情况下AsyncTask的执行顺序是串行进行的。


© 2020 Tyhoo Wu, All rights reserved.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342