AsyncTask 解析

来源:互联网 发布:淘宝网app电脑版 编辑:程序博客网 时间:2024/06/04 23:23

在系统的源代码中找到相关的文件 AsyncTask.java,在阅读该文件的注释说明,就可以大致明白该类的作用,以及其的基本用法,接下来我们来看一下该对该类的解释:

AsyncTask 使在UI 线程中的使用变得适当和简单,这个类允许执行后台的操作并且可以将结果发布到 UI线程而不需要去操作线程和handlers.

AsyncTask 被定义为一个 thread 和 handler 的帮助类,不需要构建一般的Thread的框架, AsyncTasks 理论上应该被用于一个简短的操作(至多几秒钟),如果需要长时间保持线程运行推荐使用java 库中java.util.concurrent包中的Executor,ThreadPoolExecutor,FutureTask 等api方法.

一个异步任务被定义为在后台运行的计算并且发送结果到UI 线程中,其定义了3个范型的参数:Params,Progress和Result,还定义了四个基本的方法:onPreExecute,doInBackground, onProgressUpdate,onPostExecute.

AsyncTask 必须被子类继承才能使用,子类必须至少覆盖一个方法:doInBackground,在使用中经常也需要覆盖第二个方法onPostExecute.
以下是一个例子:

private class DownloadFilesTask extends AsyncTask<URL,Integer,Long>{    protected Long doInBackground(URL... urls){        int count = urls.length;        long totalSize = 0;        for(int i = 0;i < count;i ++){            totalSize += Downloader.downloadFile(urls[i]);            publishProgress((int) ((i / (float) count) * 100));            // Escape early if cancel() is called            if (isCancelled()) break;        }        return totalSize;    }    protected void onProgressUpdate(Integer... progress){        setProgressPercent(progress[0]);    }    protected void onPostExecute(Long result) {        showDialog("Downloaded " + result + " bytes");    }}// 一旦被创建之后,使用就很简单new DownloadFilesTask().execute(url1, url2, url3);

下面介绍三个范型的参数:
* Params:这个参数将被发送到执行体
* Progress:在后台操作期间,这个参数将被不断的发送出去
* Result:后台的运算的结果

并非所有的参数类型都会被使用,如果要标记一个参数未被使用,简单的使用Void 即可.

private class MyTask extends AsyncTask<Void,Void,Void>{...}

当异步任务被执行之后,它将经历以下的四个步骤:

  • onPreExecute():在任务被执行之前在主线程中调用,这步骤通常被使用建立一个任务,例如在用户界面显示一个一个进度条,当然一般在此可以做一些初始化的工作.

  • doInBackground(Params… params):在 onPreExecute()完成执行之后在后台线程立即调用,这个步骤主要在后台线程执行耗时的操作,异步任务的参数将会传递到这一步,这个步骤必须返回计算的结果,并将结果传递到onPostExecute方法.在这一步中也可以使用publishProgress方法,去发布一个 或者多个的进度,这些值将会在onProgressUpdate 里发送在 UI线程.

  • onProgressUpdate(progress… values):调用完publishProgress方法后会在UI线程中调用
    此方法,如果不掉用publishProgress方法,则不会走该步.执行的时间选择是不确定的,当后台计算还在执行时,这个方法通常被用于显示用户界面的进度,例如它通常被用于显示进度条的动画或者在文本区域显示log.

  • onPostExecute(Result result):后台的任务运行完成之后在UI 线程中调用该方法,后台计算任务的结果传递到这作为该参数,即doInBackground的返回值.

取消任务

调用cancel(boolean mayInterruptIfRunning)可以在任何时候取消任务,调用该方法会导致isCancelled()返回true,在调用这个方法之后,onCancelled(Object) 这个方法将会被调用,而onPostExecute(Result)不会被调用,为了保证任务尽快的被取消,我们应该周期性的在doInBackground()方法中检测isCancelled()的值(例如在一个循环内).

线程规则

这个类必须遵守以下线程规则才能正常的工作:

  • AsyncTask 类必须在主线程中加载,这个已经在android 4.1以上的版本自动完成了
  • AsyncTask 类的对象必须在主线程创建
  • execute()方法必须在主线程中调用
  • 不要手动调用onPreExecute,doInBackground,onProgressUpdate,onPostExecute这四个方法
  • 一个AsyncTask 任务只能执行一次,即只能调用一次execute()方法(如果第二次调用会抛出一个异常)

内存观察

  • 在构造方法或者onPreExecute中设置成员字段,并且在doInBackground指向他们
  • 在doInBackground中设置成员字段,并且在onProgressUpdate和onPostExecute中指向他们

执行顺序

在Android 1.6 之前,AsyncTask在后台线程是串行执行的,在Android 1.6的时候,采用线程池来处理并行的任务,在Android 3.0之后,为了避免并发错误,任务在单线程上面被执行,如果真的想要进行并发操作,可以调用AsyncTask的executeOnExecutor(java.util.concurrent.Executor, Object[])来处理并行任务.

源码分析

首先当定义完异步任务之后,新建一个异步任务对象,然后调用execute方法。
我们应该明白AsyncTask是一个抽象类:

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

首先从execute()方法开始

@MainThreadpublic final AsyncTask<Params, Progress, Result> execute(Params... params) {    return executeOnExecutor(sDefaultExecutor, params);}@MainThreadpublic 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方法实际是调用executeOnExecutor,在该方法里面,首先判断此时的
状态,状态是枚举,状态一般分为三种:

  • PENDING : 指示任务还没有开始执行
  • RUNNING : 只是任务正在运行
  • FINISHED : 代表任务已经完成,即onPostExecute 已经完成

初始默认的mStatus的值为Status.PENDING,所以在最开始的时候就对AsyncTask状态进行判断,只有未执行过的任务才能执行。
接下来 便是将任务的状态设置为Status.RUNNING 状态,便开始调用AsyncTask 任务的第一个方法onPreExecute(),之后便是线程调用,首先将任务的参数传递给mWork,之后执行线程方法。
在这里就要 涉及到AsyncTask的构造方法,在此之前,我们先来了解两个基本的多线程编程的类:

  1. Callable: 为接口提供了一个call()方法,实现该接口的类可以作为被其他线程执行的任务,但是该方法是有返回值的(当然对于Runnable 的 run()方法是没有返回值的).

  2. FutureTask:表示一种抽象的可生成结果的计算,它表示的计算结果是通过callable来实现的,FutureTask将计算结果从执行计算的线程传递到获取这个运算结果的线程,可以通过get()方法来获取任务的计算结果,其中get()并不是立即就可以得到:如果任务已经完成运算的话,就会立即返回结果,否则任务就会阻塞,直到任务完成返回计算结果(当然也有可能发生异常),在FutureTask类里面有 一个done()方法,在任务线程执行完成后调用,所以程序员可以写一个FutureTask的子类来处理线程任务执行完成后的逻辑。注意 FutureTask在进入完成状态之后,将永远停留在这个状态上面(不会被再次执行),所以在AsyncTask里面,FutureTask只在构造方法里面初始化了一次,并不会在其他地方再次初始化,这也是为什么AsyncTask只能运行一次的原因(当然通过AsyncTask状态也可以保证只运行一次,否则会抛出异常),其中还有一个cancel()方法,用于取消该任务,实际上AsyncTask类的取消任务就是调用的该方法。

说了这么多,我们还是来看一下构造方法,来看一下任务的构成:

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {    Params[] mParams;}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的call方法中,首先将mTaskInvoked设为ture,表明当前任务已经被调用过了,然后便是执行AsyncTask的doInBackground方法,接着将其返回值传递postResult方法,接下来我们来看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 Handler getHandler() {    synchronized (AsyncTask.class) {        if (sHandler == null) {            sHandler = new InternalHandler();        }        return sHandler;    }}

在此方法里面发送一个MESSAGE_POST_RESULT的消息,接下来看处理该消息的地方:

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

这是一个静态的类,为了能将执行环境切换到主线程,就要求sHandler在主线程中创建,由于静态成员会在加载类的时候进行初始化,所以这就变相要求AsyncTask的类必须在主线程中加载,否则同一个进程中的的AsyncTask都将无法工作。接收到这个消息之后,就调用AsyncTask里面的finish()方法:

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

如果取消了,就调用onCancelled方法,否则就会调用onPostExecute方法,可以看到doInBackground的返回值就被传递到了onPostExecute方法。
还有就是关于FutureTask的done()方法里面的postResultIfNotInvoked(get())方法,可以看一下:

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

说明任务完成之后,如果if条件成立,依旧会执行postResult方法,
查看FutureTask的源码可知在调用cancel()方法取消任务的时候会执行这个done()方法,在发生异常的时候同样会执行这一方法,所以最终的postResult方法依旧会被执行.

现在我们回到之前FutureTask调用的地方,看看任务是如何被调用的.

exec.execute(mFuture);

对于此方法的调用,我们首先应该找到该线程池,该exec的值为 sDefaultExecutor,查看代码:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;private static class SerialExecutor implements Executor {    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();    Runnable mActive;    public synchronized void execute(final Runnable r) {        // offer(E): 将指定的元素E在此deque队列的末尾        mTasks.offer(new Runnable() {            public void run() {                try {                    r.run();                } finally {                    scheduleNext();                }            }        });        if (mActive == null) {            scheduleNext();        }    }    protected synchronized void scheduleNext() {        // poll():检索并移除此queue队列表示的队列的头部。返回null如果此queue队列为空        if ((mActive = mTasks.poll()) != null) {            THREAD_POOL_EXECUTOR.execute(mActive);        }    }}

由以上的方法可以看出,AsyncTask是串行执行,在这里,FutureTask充当了runnable的作用,接着这个任务会交给SerialExecutor的execute处理,首先将FutureTask对象插入到任务队列当中(在其中FutureTask调用其的run()方法,该方法会调用callable的call()方法),如果此时没有活动的任务,就会调用下一个AsyncTask任务,所以此时的AsyncTask默认情况下是串行执行的。
为什么是串行的呢?你看,我们首先将任务放入队列,最开始的mActive是为null,所以会执行scheduleNext()这个方法,将mActive = mTasks.poll()赋值,但是但第二个任务来的来的时候,mActive并不是为null,所以它要等到r.run()方法运行完之后,运行Finally里面的scheduleNext()方法,开始下一个任务,所以是串行的。

在AsyncTask中,存在有两个线程池和一个Handler,其中SerialExecutor 用于任务的排队,THREAD_POOL_EXECUTOR 用于执行真正的任务,InternalHandler用于将执行环境从线程池切换到主线程.

至此关于AsyncTask的源码解析就结束了,希望会有所帮助.

参考文献:
1. Android开发艺术探索
2. AsyncTask源代码浅析(一) http://blog.csdn.net/chunqiuwei/article/details/40405277