理解 AsyncTask 原理

本人只是 Android小菜一个,写技术文档只是为了总结本身在最近学习到的知识,历来不敢为人师,若是里面有些不正确的地方请你们尽情指出,谢谢!java

1. 概述

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

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

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设计的帮助类,其容许在后台线程执行耗时任务并把处理进度和结果发布到主线程,不过通常适用于执行时间相对较短的任务,通常执行时间不要超过几秒。app

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));
    }
}
复制代码

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

经过这段代码能够看到要使用AsyncTask实行异步任务是很是容易的,只须要作两件事:异步

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

2.2 参数介绍

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

public abstract class AsyncTask<Params, Progress, Result> { ... }
复制代码

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

参数声明 含义 做用 产生处/调用处 注意事项
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的内部原理和在执行任务过程当中是如何进行线程切换的。固然,还有不少细节在文中并无说起,但这并不会影响对其原理的理解,“忽略无关细节”也是咱们平时学习源码过程当中常常采用的方法。

相关文章
相关标签/搜索