AsyncTask | 源码篇

1,100 阅读12分钟

AsyncTask.png

相关推荐:

概述

AsyncTask是一个轻量级选手,适合处理轻量级的后台任务。处理过程中还可以把处理的进度反馈到主线程中,方便我们更新UI,不需要我们去操作 handler,在早期 Android 版本中是十分方便的工具。今天我们来一起来阅读一下源码吧 ~

1. 类注释

 * <p>AsyncTask enables proper and easy use of the UI thread. This class allows you
 * to perform background operations and publish results on the UI thread without
 * having to manipulate threads and/or handlers.</p>

AsyncTask 更加正确和方便地使用UI线程,它允许你执行后台的操作并且把结果反馈到UI线程上,而不需要操控线程和Handlers。

 * <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link 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 <code>java.util.concurrent</code> package such as {@link Executor},
 * {@link ThreadPoolExecutor} and {@link FutureTask}.</p>

AsyncTask 设计来成为 Thread 和 Handler 的辅助类,并不是一个通用的线程框架。它适合用于一些短时间的操作,例如几秒钟的一个操作。如果你想执行一个长时间的耗时操作,可以使用 java.util.concurrent 下的APIS。

 * <p>An asynchronous task is defined by a computation that runs on a background thread and
 * whose result is published on the UI thread. An asynchronous task is defined by 3 generic
 * types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
 * and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,
 * <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>

通常密集型计算任务设计成一个异步的后台任务,它运行在后台线程,并把最终的结果展示到UI上。一个 AsyncTask 定义了3中参数泛型:Params,Progress,Result,和4个步骤:onPreExecute,doInBackgroud,onProgressUpdate,onPostExecute。

 * <h2>AsyncTask's generic types</h2>
 * <p>The three types used by an asynchronous task are the following:</p>
 * <ol>
 *     <li><code>Params</code>, the type of the parameters sent to the task upon
 *     execution.</li>
 *     <li><code>Progress</code>, the type of the progress units published during
 *     the background computation.</li>
 *     <li><code>Result</code>, the type of the result of the background
 *     computation.</li>
 * </ol>

AsyncTask 定义的三个参数泛型:

  • Params,输入参数,执行任务时发送到后台任务的参数类型
  • Progress,后台任务执行时,更新进度的参数类型
  • Result,后台任务执行完毕的输出结果
 * <h2>The 4 steps</h2>
 * <p>When an asynchronous task is executed, the task goes through 4 steps:</p>
 * <ol>
 *     <li>{@link #onPreExecute()}, invoked on the UI thread before the task
 *     is executed. This step is normally used to setup the task, for instance by
 *     showing a progress bar in the user interface.</li>
 *     <li>{@link #doInBackground}, invoked on the background thread
 *     immediately after {@link #onPreExecute()} finishes executing. This step is used
 *     to perform background computation that can take a long time. The parameters
 *     of the asynchronous task are passed to this step. The result of the computation must
 *     be returned by this step and will be passed back to the last step. This step
 *     can also use {@link #publishProgress} to publish one or more units
 *     of progress. These values are published on the UI thread, in the
 *     {@link #onProgressUpdate} step.</li>
 *     <li>{@link #onProgressUpdate}, invoked on the UI thread after a
 *     call to {@link #publishProgress}. The timing of the execution is
 *     undefined. This method is used to display any form of progress in the user
 *     interface while the background computation is still executing. For instance,
 *     it can be used to animate a progress bar or show logs in a text field.</li>
 *     <li>{@link #onPostExecute}, invoked on the UI thread after the background
 *     computation finishes. The result of the background computation is passed to
 *     this step as a parameter.</li>
 * </ol>

异步任务通过4个步骤去执行:

  • onPreExecute():任务开启前调用在UI线程上。通常在此方法上做一些初始化工作,例如展示一个进度条。
  • doInBackground():在onPreExecute()执行完成后立即执行,在子线程上。这个方法内通常做一些耗时的后台计算,上面的泛型类型参数将传递到这个方法,计算结果必须由此步骤返回,并传递回最后一步。在这个方法内,还可以使用 publishProgress() 方法发布一个或者多个的进度单位,就是回调到方法 onProgressUpdate() 上,此方法运行在UI线程。
  • onProgressUpdate():在调用 publishProgress() 方法后执行,但是执行的时间不确定。通常用户在后台计算执行是,显示任务进度。
  • onPostExecute():在后台计算任务结束后执行。doInBackgroud() 的返回值将传递给此方法。
 * <h2>Cancelling a task</h2>
 * <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking
 * this method will cause subsequent calls to {@link #isCancelled()} to return true.
 * After invoking this method, {@link #onCancelled(Object)}, instead of
 * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])}
 * returns. To ensure that a task is cancelled as quickly as possible, you should always
 * check the return value of {@link #isCancelled()} periodically from
 * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)</p>

任务可以调用 cancel(boolean) 去取消。取消后导致 isCancelled() 方法返回 true,并且会调用 onCancelled() 而不是 onPostExecute() 在方法 doInBackgroud() 完成后。为了确保能尽快地 cancel 任务,在 doInBackgroud 中应该时刻去判断任务是否被取消,尤其是在一个循环内,否则会做很多无用功。

 * <h2>Threading rules</h2>
 * <p>There are a few threading rules that must be followed for this class to
 * work properly:</p>
 * <ul>
 *     <li>The AsyncTask class must be loaded on the UI thread. This is done
 *     automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li>
 *     <li>The task instance must be created on the UI thread.</li>
 *     <li>{@link #execute} must be invoked on the UI thread.</li>
 *     <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute},
 *     {@link #doInBackground}, {@link #onProgressUpdate} manually.</li>
 *     <li>The task can be executed only once (an exception will be thrown if
 *     a second execution is attempted.)</li>
 * </ul>

AsyncTask 需要遵循一些线程规则才能正常工作。

  • AndroidVersion<16的时候,AsyncTask 必须在UI线程中加载,因为需要获取到UI线程的Handler,不过在16后就不需要了,因为16后面默认加载主线程的Handler。
  • 实例必须在UI线程上被创建
  • execute 必须在UI线程上被调用
  • 请不要人为调用:onPreExecute,doInBackgroud,onProgressUpdate,onPostExecute
  • 任务只能被执行一次,执行多次会抛出异常
 * <h2>Memory observability</h2>
 * <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following
 * operations are safe without explicit synchronizations.</p>
 * <ul>
 *     <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them
 *     in {@link #doInBackground}.
 *     <li>Set member fields in {@link #doInBackground}, and refer to them in
 *     {@link #onProgressUpdate} and {@link #onPostExecute}.
 * </ul>

保证一些方法的执行顺序,构造函数会先执行,然后到 onPreExecute,接着是 doInBackgroud,然后多次调用 onProgressUpdate ,最后是 onPostExecute。

 * <h2>Order of execution</h2>
 * <p>When first introduced, AsyncTasks were executed serially on a single background
 * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
 * to a pool of threads allowing multiple tasks to operate in parallel. Starting with
 * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single
 * thread to avoid common application errors caused by parallel execution.</p>
 * <p>If you truly want parallel execution, you can invoke
 * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with
 * {@link #THREAD_POOL_EXECUTOR}.</p>

在一开始,AsyncTask 任务的执行是串行执行,使用一条工作线程,从 Android-DONUT,AndroidVersion4 开始,使用线程池来创建线程,和允许并行执行任务,而从 AndroidHoneyComb,AndroidVersion11开始,支持串行和并行,默认提交是串行执行任务,避免了多线程并发导致脏资源的情况出现。当然,如果你想使用并行执行,可以调用 executeOnExecutor(THREAD_POOL_EXECUTOR) 提交。

2. 串行执行

两个线程池

/**
 * An {@link Executor} that executes tasks one at a time in serial
 * order.  This serialization is global to a particular process.
 */
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

首先看到一个Executor接口,由SerialExecutor类实现。接着看一下接口的实现:

private static class SerialExecutor implements Executor {
    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();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

SerialExecutor类里面用到一个双端队列ArrayDeque?

ArrayDeque不是线程安全的。 ArrayDeque不可以存取null元素,因为系统根据某个位置是否为null来判断元素的存在。 当作为栈使用时,性能比Stack好;当作为队列使用时,性能比LinkedList好。

SerialExecutor 内部的实现,内部不复杂,一个双端队列数据结构,从尾部 offer 添加任务,poll 出队列的第一个任务进行执行。而SerialExecutor 只是一个调用任务的线程池而已,最后的执行是交由:THREAD_POOL_EXECUTOR.execute(mActive);,真正执行任务的是一个 ThreadPoolExecutor 线程池,静态加载的时候就被初始化好。所以这里的SerialExecutor线程池只是用来保证任务逐一被执行。

public static final Executor THREAD_POOL_EXECUTOR;
static {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
            sPoolWorkQueue, sThreadFactory);
    threadPoolExecutor.allowCoreThreadTimeOut(true);
    THREAD_POOL_EXECUTOR = threadPoolExecutor;
}

任务的提交

AsyncTask.execute:

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

再查到,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;
    exec.execute(mFuture);
    return this;
}

首先对任务的状态进行判断,从这里可以看到任务只能被执行一次,否则多次调用execute会抛出异常。还有注意看到有一个 @MainThread 的注解,应该是对方法执行的位置进行判断是否是主线程。接着初始化任务,调用 onPreExecute() 方法,调用 传入参数 exec.execute(mFuture) 当然默认就是上面的 SerialExecutor 实例。

worker & future

再回到我们的 AsyncTask.execute(),mWorker.mParams = params,初始化参数,然后,exec.execute(mFuture),这里的 wWorker 和 mFuture 是什么呢?我们找到定义和初始化的地方:

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Result result = null;
            try {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                result = doInBackground(mParams);
                Binder.flushPendingCommands();
            } catch (Throwable tr) {
                mCancelled.set(true);
                throw tr;
            } finally {
                postResult(result);
            }
            return 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);
            }
        }
    };
}

mWorker 是 WorkerRunnable 的实例对象,WorkerRunnable 是实现了 Callable 接口,其还内部存储了初始化的参数 params,在上面的构造方法中,mWorker 用一个匿名内部类实现了 callable 的 call() ,其里面就是 doInBackgroud 方法的执行,最后返回一个 result,调用 postResult(result),后面再说这个方法。

mFuture 是 FutureTask 的一个示例,传入了 mWorker 任务,exec.excute() 后,便开始执行任务,mFuture 内部 postResultIfNotInvoked(get());,get() 是 FutureTask 的方法,一直阻塞运行 mWorker 任务,得到结果后,postResultIfNotInvoked(result)

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

mTaskInvoked 是一个原子布尔值,多线程下安全的一个Boolean值,这里标记是否已经回调过结果,没有则:postResult(result)

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

handler

看到这里,handler ?没错,又是通过它进行回调到别的线程上,那是别的什么线程?我们前面知道,结果反馈的方法 onPostExecute 运行在UI线程,我们查到这个 Handler 的源码:

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }
    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            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;
        }
    }
}

bingo,这里的Handler是UI线程的,当然在处理方法里面我们也看到了,进度条的更新方法,和结果的回调方法finish

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

这里,如果正常结束任务就是回调 onPostExecute,如果中途取消任务,则最后回调的是 onCancelled。

进度刷新

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

调用在 worker 线程,通过 handler 回调到UI线程上。

取消任务

public final boolean cancel(boolean mayInterruptIfRunning) {
    mCancelled.set(true);
    return mFuture.cancel(mayInterruptIfRunning);
}

真的可以取消正在运行的任务吗?偷偷看看注释:

* <p>Attempts to cancel execution of this task.  This attempt will
* fail if the task has already completed, already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.</p>

你个糟老头坏得很,注释很清楚,有可能会取消失败。最终调用的是 FutureTask 的cancel方法,摘个大神的源码分析结果:FutureTask介绍及使用

cancel()方法改变了futureTask的状态位,如果传入的是false并且业务逻辑已经开始执行,当前任务是不会被终止的,而是会继续执行,直到异常或者执行完毕。如果传入的是true,会调用当前线程的interrupt()方法,把中断标志位设为true。

事实上,除非线程自己停止自己的任务,或者退出JVM,是没有其他方法完全终止一个线程的任务的。mayInterruptIfRunning=true,通过希望当前线程可以响应中断的方式来结束任务。当任务被取消后,会被封装为CancellationException抛出。

先不深究 FutureTask 的源码,反正我们已经知道,这个 cancel 方法不靠谱,源码的注释接着也墙裂建议我们,在 doInBackgroud 方法里面,去做是否任务被取消的判断,尽快地去结束任务。

还有一个看到上面的分析说,当任务被取消后,会被封装为CancellationException抛出。 有点眼熟,在 mFuture 初始化中,有捕抓这个异常:

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);
        }
    }
};

当捕抓到了,就说明取消了,以一个 null 的参数传入 postResultIfNotInvoked,继续上面的逻辑。

3. 并行执行

并行任务可以这样提交:executeOnExecutor(java.util.concurrent.Executor, Object[]),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;
    exec.execute(mFuture);
    return this;
}

接下来的逻辑就跟上面的一样咯。

4. 总结

  • AsyncTask 串行还是并行跟版本有关,AndroidVersion<4,串行,11>AndroidVersion>=4,并行,并行后,出现很多多线程并发访问资源的问题,AndroidVersion>=11后,可以是并行,也可以是串行,而且并行串行都是用同一个线程池去执行。
  • AsyncTask 的 cancel 不保证一定可以取消任务,需要在 doInBackgroud中时刻去判断任务是否别取消,尽早结束任务。
  • 按照注释,严格地在指定线程上开启任务
  • AsyncTask如果在Activity中使用,要注意内存泄漏问题,和屏幕旋转问题
  • 如果任务是串行,必须要等前面任务执行完毕后,才能开始启动下一个
  • AsyncTask虽然可以以简短的代码实现异步操作,但是正如本文提到的,你需要让AsyncTask正常工作的话,需要注意很多条条框框。推荐的一种进行异步操作的技术就是使用Loaders。

码字不易,方便的话素质三连,或者关注我的公众号 技术酱,专注 Android 技术,不定时推送新鲜文章,如果你有好的文章想和大家分享,欢迎关注投稿!

技术酱