理解 AsyncTask 原理

4,146 阅读14分钟

本人只是 Android小菜一个,写技术文档只是为了总结自己在最近学习到的知识,从来不敢为人师,如果里面有些不正确的地方请大家尽情指出,谢谢!

1. 概述

之前讲解了能够在后台工作线程中执行耗时任务的IntentService框架,在这里我们继续学习Android提供的另外一个异步执行任务的框架AsyncTask,它和IntentService既有相似点也有不同点,其相似之处在于都能在新的线程中执行耗时任务防止阻塞主线程,不同之处在于AsyncTask能够追踪任务的执行过程和结果并在主线程中显示出来。

我们先用一个简单的示例演示下该如何使用AsyncTask,再通过源代码分析其内部实现原理。

2. AsyncTask 的使用方式

在开始使用前,先来看下Android SDK中对AsyncTask的介绍,请声明如下:

/**
 * <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>
 *
 * <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>
 */
public abstract class AsyncTask<Params, Progress, Result> { ... }

AsyncTask是一个基于ThreadHandler设计的帮助类,其允许在后台线程执行耗时任务并把处理进度和结果发布到主线程,不过一般适用于执行时间相对较短的任务,一般执行时间不要超过几秒。

2.1 使用示例

先来看一段示例代码:

private class DownloadAsyncTask extends AsyncTask<String, Integer, Long> {
    @Override
    public void onPreExecute() {
        mProgress.setVisibility(View.VISIBLE);
        mProgress.setMax(100);
        mProgress.setProgress(0);
    }

    @Override
    public Long doInBackground(String... uris) {
        int count = uris.length;
        long size = 0;
        for (int i = 1; i <= count; i ++) {
            try {
                // 休眠5秒模拟下载过程
                Thread.sleep(5 * 1000);
                // 假设每个下载文件的大小为(序号*100)
                size += i * 100;
                // 发布进度更新
                publishProgress( (100* i )/count);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
        return size;
    }

    @Override
    public void onProgressUpdate(Integer... progress) {
        mProgress.setProgress(progress[0]);
    }

    @Override
    public void onPostExecute(Long result) {
        mText.setText(String.valueOf(result));
    }
}

这段代码主要是模拟了文件下载过程,在下载过程中实时更新进度,并在下载完成后在界面显示下载文件的总大小。

通过这段代码可以看到要使用AsyncTask实行异步任务是非常容易的,只需要做两件事:

  • 确定在整个处理过程中需要的参数类型,包括Params,ProgressResult,分别对应着输入参数、进度参数和结果参数。
  • 实现必要的回调方法,其中必须是实现的是doInBackground,耗时任务正是在这里进行处理的,可以想象doInBackground一定是在子线程里进行的;其他可选实现方法包括onPreExecute,onProgressUpdateonPostExecute,这些在示例中都参与了UI的更新,所以一定是在主线程中进行的。

2.2 参数介绍

首先看下AsyncTask中对参数的声明:

public abstract class AsyncTask<Params, Progress, Result> { ... }

可以发现AsyncTask中使用的都是泛型参数,在使用过程中要根据需求选择合适的参数类型,在示例中使用的参数类型分别是String,IntegerLong,如果某一个参数是不需要的,可以用Void来表示,下面通过一个表格来对每个参数进行说明:

参数声明 含义 作用 产生处/调用处 注意事项
Params 输入参数 任务开始执行时客户端发送开始参数 execute()中发送,在doInBackground()中调用。 可变参类型
Progress 过程参数 任务后台执行过程中服务端发布的当前执行进度 在doInBackground()中产生并通过publishProgess()发送,在onProgressUpdate()调用。 可变参类型
Result 结果参数 任务执行完成后服务端发送的执行结果 在doInBackground()中产生并在onPostExecute()中调用。

参数类型不能是基本数据类型,要使用对应的封装类型,例如示例的ProgressResult参数使用的IntegerLong而不是intlong

2.3 回调接口

AsyncTask中有几个重要的回调接口,下面分别介绍:

  • onPreExecute(): 在主线程中运行,主要是在后台线程开始执行任务之前进行某些UI的初始化,例如进度条的显示,可选择实现,其声明如下:
/**
 * Runs on the UI thread before {@link #doInBackground}.
 */
@MainThread
protected void onPreExecute() { }
  • doInBackground: 在后台线程中运行,主要是接收客户端发送过来的参数,在后台执行耗时任务并发布执行进度和执行结果,例如文件下载任务,是在使用过程中必须实现的接口,其声明如下:
/**
 * Override this method to perform a computation on a background thread. The
 * specified parameters are the parameters passed to {@link #execute}
 * by the caller of this task.
 *
 * This method can call {@link #publishProgress} to publish updates
 * on the UI thread.
 *
 * @param params The parameters of the task.
 *
 * @return A result, defined by the subclass of this task.
 *
 */
@WorkerThread
protected abstract Result doInBackground(Params... params);
  • publishProgress: 在后台线程中运行,主要是发布任务的当前执行进度,以方便在主线程中显示,不需要重新实现直接调用,其声明如下:
/**
 * This method can be invoked from {@link #doInBackground} to
 * publish updates on the UI thread while the background computation is
 * still running. Each call to this method will trigger the execution of
 * {@link #onProgressUpdate} on the UI thread.
 *
 * {@link #onProgressUpdate} will not be called if the task has been
 * canceled.
 *
 * @param values The progress values to update the UI with.
 */
@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}
  • onProgressUpdate: 在主线程中运行,主要是更新当前的执行进度,例如更新进度条进度,可选择实现,其声明如下:
/**
 * Runs on the UI thread after {@link #publishProgress} is invoked.
 * The specified values are the values passed to {@link #publishProgress}.
 *
 * @param values The values indicating progress.
 */
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onProgressUpdate(Progress... values) { }
  • onPostExecute: 在主线程中运行,主要是接收doInBackground返回的执行结果并在主线程中显示,例如显示下载文件大小,可选择实现,其声明如下:
/**
 * <p>Runs on the UI thread after {@link #doInBackground}. The
 * specified result is the value returned by {@link #doInBackground}.</p>
 * 
 * <p>This method won't be invoked if the task was cancelled.</p>
 *
 * @param result The result of the operation computed by {@link #doInBackground}.
 */
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onPostExecute(Result result) { }

通过一个表格来总结下这些重要的回调方法:

回调方法 运行线程 作用 执行顺序 是否需要重新实现
onPreExecute 主线程 在开始执行后台任务前进行初始化 首先开始执行 可选
doInBackground 后台线程 执行后台耗时任务完成后返回结果 onPreExecute 执行完成后执行 必须实现
publishProgress 后台线程 在执行任务过程中发布执行进度 在 doInBackground 中执行 无须实现,直接调用。
onProgressUpdate 主线程 接收进度并在主线程处理 在 publishProgress 之后执行 可选
onPostExecute 主线程 接收执行结果并在主线程处理 在 doInBackground 执行完成后执行 可选

3. AsyncTask 的原理

前面已经大致分析了AsyncTask的使用方法以及重要的回调方法,现在来看下其内部是如何实现的,主要关注两方面的信息:如何进行线程的切换和如何组织调用上述的回调方法。

客户端是通过AsyncTask.execute()来开启异步任务的,我们就以这个为切入点来分析,首先看下execute()做了什么:

/**
 * Executes the task with the specified parameters. The task returns
 * itself (this) so that the caller can keep a reference to it.
 * 
 * <p>Note: this function schedules the task on a queue for a single background
 * thread or pool of threads depending on the platform version.  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
 * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
 * executed on a single thread to avoid common application errors caused
 * by parallel execution.  If you truly want parallel execution, you can use
 * the {@link #executeOnExecutor} version of this method
 * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
 * on its use.
 *
 * <p>This method must be invoked on the UI thread.
 */
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

execute()内部直接调用executeOnExecutor()来开启异步任务,这点我们稍后分析,其声明中有段话需要特别留意:这个任务是放在一个单独的后台线程中顺序执行还是放在线程池中并行执行,和具体平台有关。在AsyncTask最初被引进的时候是在一个单独的后台线程中顺序执行的,后续几次改变,目前默认是顺序执行的。如果确实想并行执行的话,就需要直接调用executeOnExecutor()并且传入合适的Executor。 现在重新回到对execute()的分析,由于其内部调用的是executeOnExecutor(sDefaultExecutor, params),那就来看下这个函数在做什么?

/**
 * <p>This method must be invoked on the UI thread.
 *
 * @param exec The executor to use.  {@link #THREAD_POOL_EXECUTOR} is available as a
 *              convenient process-wide thread pool for tasks that are loosely coupled.
 * @param params The parameters of the task.
 *
 * @return This instance of AsyncTask.
 *
 * @throws IllegalStateException If {@link #getStatus()} returns either
 *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
 *
 */
@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;
    // 回调方法中首先被调用的方法,由于"execute()"是在主线程中执行的,
    // 目前为止也没有进行线程的切换,所以"onPreExecute"也是在主线程中执行的。
    onPreExecute();

    // 通过几次“封装”,把参数传入"Executor"内部,等待执行。
    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}

整段代码的前半部分比较好理解,就是进行状态判断和更新,然后调用onPreExecute接口,接下来的两行代码咋一看实在让人费解。不要急不要慌,一步步来分析,首先要搞清楚mWorker是什么,来看下相关代码:

private final WorkerRunnable<Params, Result> mWorker;
    
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;
    }
};

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
    Params[] mParams;
}

可以看到mWorkerWorkerRunnable的对象,而WorkerRunnable又拓展了Callable接口,看下Callable接口是什么:

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

在这段说明里,把CallableRunnable进行了对比,它们都是用来在其他线程里执行的,不同之处在于Callable中的call()方法能够返回结果和检查异常,而Runnable中的run没有这个功能,所以可以简单地把Callable当做Runnable来看待,只是被执行的方法是call()而且可以返回结果。由于WorkerRunnable继承了Callable,又新增了参数,就可以把mWorker当做一个既可以接受参数有可以在执行后返回结果的Runnable对象。 现在来看mFuture是什么,相关代码如下:

private final FutureTask<Result> 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);
        }
    }
};

可以看到mFutureFutureTask对象并且是利用mWorker作为参数进行构造的,看下FutureTask是什么:

/**
 * A cancellable asynchronous computation.  This class provides a base
 * implementation of {@link Future}, with methods to start and cancel
 * a computation, query to see if the computation is complete, and
 * retrieve the result of the computation.  The result can only be
 * retrieved when the computation has completed; the {@code get}
 * methods will block if the computation has not yet completed.  Once
 * the computation has completed, the computation cannot be restarted
 * or cancelled (unless the computation is invoked using
 * {@link #runAndReset}).
 *
 * <p>A {@code FutureTask} can be used to wrap a {@link Callable} or
 * {@link Runnable} object.  Because {@code FutureTask} implements
 * {@code Runnable}, a {@code FutureTask} can be submitted to an
 * {@link Executor} for execution.
 */
public class FutureTask<V> implements RunnableFuture<V> { 
    // 核心方法,其他方法省略。
    public void run() {
    if (state != NEW ||
        !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    // 执行封装的 Callable.call() 方法并得到计算结果
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
}

FutureTask是用来进行异步计算的,这个计算可以开始、取消、查询等等,而且可以用来封装Callable,其中核心的方法就是run(),通过上面的代码逻辑可以看到在run()中最核心的就是调用所封装的Callable.call()来进行计算并得到结果。

mFuture所封装的正是mWorker,所以其最终结果就是调用mWorkder.call()方法,现在在返回来看看之前不理解的那几行代码:

mWorker.mParams = params;
exec.execute(mFuture);

这两行代码就是把mFuture交给线程池来执行,完成了线程的切换,也就是说mFuture的执行是在后台线程中进行的,mFuture最终会调用mWorker.call()方法,再回过头来看看mWorker.call()做了什么:

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

最终会在新的线程里执行doInBackground(),执行之后就通过postResult()分发执行结果:

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

private static class AsyncTaskResult<Data> {
    final AsyncTask mTask;
    final Data[] mData;

    AsyncTaskResult(AsyncTask task, Data... data) {
        mTask = task;
        mData = data;
    }
}

postResult()会通过getHandler()来构建Message对象,最终把结果包装成AsyncTaskResult进行分发,那getHandler()返回的是哪个Handler呢?

private Handler getHandler() {
    return mHandler;
}

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
        ? getMainHandler()
        : new Handler(callbackLooper);
...
}

// 调用这个构造函数来创建AsyncTask实例
public AsyncTask() {
    this((Looper) null);
}

从这段代码可以看到getHandler()返回的是mHandler对象,而mHandler是在AsyncTask里进行创建的,由于在创建AsyncTask实例是并没有传入callbackLooper,最终的效果就是mHandler是通过getMainHandler()实例化的,得到的是主线程的Handler,其代码如下:

private static Handler getMainHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler(Looper.getMainLooper());
        }
        return sHandler;
    }
}

兜兜转转,doInBackground()的执行结果会交给InternalHandler来处理:

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) {
            case MESSAGE_POST_RESULT:
                // There is only one result
                // 处理 doInBackground()的执行结果
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                // 处理 publishProgress()发布的执行进度
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

InternalHandler接收到doInBackground()的执行结果后,会调用result.mTask.finish()来继续处理结果,实际调用的就是AsyncTask.finish(),来看看其代码:

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        // 正常结束时调用 onPostExecute()处理执行结果
        onPostExecute(result);
    }
    // 更新状态为“完成”
    mStatus = Status.FINISHED;
}

在这里终于看到onPostExecute()得到调用了,由于mHandler是用主线程HandleronPostExecute()也就是在主线程中执行的了。 到这里,终于理清了execute()->onPreExecute()->doInBackground()->onPostExecute()的整个调用过程了,也明白了在整个过程中的线程切换。

细心的读者会发现onProgressUpdate()在哪里得到调用的并没有提及,这个问题就留给大家去完成吧,相信通过前面层层解析,这个问题会很容易得到解决!

4. 总结

本文先通过一个简单的模拟下载任务的示例展示了AsyncTask的使用方法并分析其参数类型和重要回调方法,最后通过层层递进地分析源码,明白了AsyncTask的内部原理和在执行任务过程中是如何进行线程切换的。当然,还有很多细节在文中并没有提及,但这并不会影响对其原理的理解,“忽略无关细节”也是我们平时学习源码过程中经常采用的方法。