AsyncTask简介(二) 源码剖析

1 引言

在前面的一片文章AsyncTask简介(一) 基本概念和用法中,我们对AsyncTask的使用方法进行了简单的介绍。
本文从源码的角度对AsyncTask进行剖析,学习AsyncTask的设计思想和原理,明白了设计思想和原理。便会对AsyncTask有更深刻的认识。

2 AsyncTask的构造函数

使用AsyncTask,一般来说分三步,
1. 重写AsyncTask的各回调方法
2. 构建AsyncTask实例
3. 调用AsyncTask的execute方法
所以我们先来学习一下AsyncTask的实例是如何被构建的。
AsyncTask的构造函数如下:

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            ...
            return postResult(doInBackground(mParams));
        }
    };

    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            ...
        }
    };
}

构造函数很简单,首先构建了一个WorkerRunnable类型的对象mWorker,并以mWorker为参数构建了一个FutureTask对象mFuture。

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

WorkerRunnable是一个抽象类,仅在Callable接口的基础上增加了mParams属性。
mWorker是一个Callable对象,mFuture是用mWorker构建的FutureTask对象。很显然mWorker中的内容将会被提交到一个线程池中执行。
mWorker的call方法中有一行代码:

return postResult(doInBackground(mParams));

我们在这里见到了我们熟悉的doInBackground。
虽然我们还没有学习整个AsyncTask的代码,但是看到这里,我们几乎可以断定AsyncTask的执行架构是下面这样。

    这里写图片描述

关于线程池,Callable,FutureTask等概念,大家可以百度Java多线程相关的知识。在这里,大家只需要知道,mFuture是对mWorker的一次封装,mFuture将会被提交到线程池执行,最终mWorker里的call方法将会在线程池中的某个线程中被执行。

3 AsyncTask的execute方法

在第2节中,我们已经构建完了AsyncTask实例,下面只需要在UI线程中调用execute即可运行异步任务。所以再来看一下execute函数:

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

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
    ...
    onPreExecute();
    ...
    mWorker.mParams = params;
    exec.execute(mFuture);
    ...
}

execute调用 executeOnExecutor(sDefaultExecutor, params);
executeOnExecutor(sDefaultExecutor, params)的方法体中重点关注三个步骤:
1. onPreExecute(),可以看出,在调用了execute以后,onPreExecute方法得到了立即执行,所以我们在这里做AsyncTask的准备工作。因为execute是在UI线程中调用,所以onPreExecute也是在UI线程中调用的,可以在onPreExecute方法中安全的更新UI。
2. mWorker.mParams = params。params是我们在调用execute的时候传递的参数,mWorker持有了这个参数列表。
3. exec.execute(mFuture),exec实际上就是sDefaultExecutor,调用sDefaultExecutor的execute方法执行mFuture。这一步也是AsyncTask的核心的步骤了,我们下面详细分析一下这一步。

4 AsyncTask的执行器sDefaultExecutor

我们在第三节中讲到,调用AsyncTask的静态方法execute,最终都将调用sDefaultExecutor的execute方法。我就先来认识一下sDefaultExecutor:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

public static final Executor SERIAL_EXECUTOR = new 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);
        }
    }
}

sDefaultExecutor是一个SerialExecutor实例。SerialExecutor实现了Executor接口。Executor也是Java的current包中的一员。他的内容非常简单,只有execute函数接口。
SerialExecutor 包含两个属性和两个方法:
1.属性mTasks, 一个双向队列,队列中的元素是Runnable对象:ArrayDeque< Runnable > mTasks,mTask是execute的任务队列,每一个任务是一个Runnable对象。
2.属性mActive,一个Runnable对象,任务队列中当前正在被执行的任务。
3.方法execute(Runnable r),我们在第三节中讲到调用AsyncTask实际上就是调用sDefaultExecutor.execute(mFuture)。mFuture是一个FutureTask对象,封装了Callable对象mWorker,由于FutureTask同时实现了Runnable和Callable接口,可以将Callable对象转换成Runnable对象。所以,实际上sDefaultExecutor.execute的参数是之前的Callable对象mWorker。在execute函数中又新建了一个Runnable对象,该Runnable对象的run方法,就是mWorker的call方法,但是增加了finally代码块,在执行完run方法以后,将自动执行scheduleNext方法。在构建完该Runnable对象以后,将此Runnable对象放入任务队列mTask。若第一次执行AsyncTask,mActive为空,调用scheduleNext方法。
4. 方法scheduleNext,从任务列表mTask中取出一个任务mActive,并调用THREAD_POOL_EXECUTOR.execute(mActive)。

public static final Executor THREAD_POOL_EXECUTOR
 = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

THREAD_POOL_EXECUTOR是一个线程池。所以mActive将会被放到这个线程池中执行。

讲到这里,我们已经把AsyncTask的执行框架梳理完毕了。我们还是对照着图来说明一下。
这里写图片描述

sDefaultExecutor是一个SerialExecutor对象,SerialExecutor类中有一个任务队列mTask,队列成员是待执行的任务,他们都是Runnable对象。而我们在构建AsyncTask实例的时候传入的mWorker将会通过FutureTask类转换成Runnable对象放入任务队列mTask。
**首次执行execute的时候,将调用scheduleNext函数,将mTask队列的头放入已经准备好的线程池中THREAD_POOL_EXECUTOR中执行。执行任务就是执行该Runnable的run方法(由mWorker的call方法适配而来)。所以我们在构建mWorker对象的时候的postResult(doInBackground)方法将在线程池中被执行。
到这里我们看到为什么doInBackground是如何在后台线程中被调用的。**

我们还可以看到sDefaultExecutor 是一个静态对象,所以不管调用任何一个AsyncTask的execute方法,都将在同一个实例sDefaultExecutor上调用execute方法。而且提交到SerialExecutor的任务队列的元素是被串行执行的,当前一个任务元素执行完以后,下一个任务元素才可以被执行。这就是为什么不能在AsyncTask中做特别耗时操作的原因.

5 后台线程是如何与主线程通信的?

我们貌似已经讲完了AsyncTask的执行流程。但是只涉及到了onPreExecute和doInBackground。
我们接下来就看一下onUpdateProgress和onPostExecute又是如何执行的。

5.1 AsyncTask的Handler

我们都知道AsyncTask是对Handler和Thread的封装。Thread,我们前面已经看到了,是将doInBackground封装在FutureTask中,提交到线程池THREAD_POOL_EXECUTOR。那Handler呢?
在AsyncTask中有一个属性sHandler:

从其构造函数中可以看出,InternalHandler被绑定到 Looper.getMainLooper()这个Looper上,这表明InternalHandler是绑定到主线程上的。handleMessage方法将在主线程中被调用。
而若想获得InternalHandler实例

private static InternalHandler sHandler;

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

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

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

5.2 利用AsyncTask中的Handler进行线程间通信

sHandler是一个单例,通过getHandler方法获取。所有的AsyncTask公用同一个sHandler。
InternalHandler只处理两个消息:
1) MESSAGE_POST_PROGRESS:
当publishProgress被调用的时候

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

将会调用sHandler发送一个MESSAGE_POST_PROGRESS消息,InternalHandler接收到这个消息以后在handleMessage中调用result.mTask.onProgressUpdate(result.mData)进行处理,这样用户重写的onProgressUpdate将会被调用。我们前面说过InternalHandler是绑定到主线程中的,所以onProgressUpdate是主线程中被调用的。可以安全更新UI。

2) MESSAGE_POST_RESULT:
在第2节中介绍mWorker的call方法:

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
        ...
        return postResult(doInBackground(mParams));
    }
};

我们前面已经介绍过call方法将会被放到线程池中执行。doInBackground将由AsyncTask的子类重写。其返回值将调用postResult方法。

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

postResult方法将调用sHandler,发送一个MESSAGE_POST_RESULT消息
hanleMessage在处理MESSAGE_POST_RESULT则会调用
result.mTask.finish(result.mData[0]);

private void finish(Result result) {
    ...
    onPostExecute(result);
    ...
}

所以线程池执行完doInBackground以后,会调用postResult,postResult则会借助于InternalHandler发送一个MESSAGE_POST_RESULT消息,hanleMessage处理了这个消息,调用onPostExecute,InternalHandler是绑定到主线程中的,所以onPostExecute是主线程中被调用的。可以安全更新UI。

6 用AsyncTask执行Runnable对象

AsyncTask还提供一个简单易用的使用方法,无需创建AsyncTask实例,使用AsyncTask的静态方法。

public static void execute(Runnable runnable) {
    sDefaultExecutor.execute(runnable);
}

调用execute方法直接执行一个Runnable对象。
直接将一个Runnable对象放入sDefaultExecutor的任务队列mTask中。由线程池THREAD_POOL_EXECUTOR直接执行。
比较一下AsyncTask的两种执行异步任务的方法。
1. 重写doInBackground方法。将doInBackground方法封装在一个Callable对象中,然后经由FutureTask转换成一个Runnable对象,再放入任务队列mTask中,提交到THREAD_POOL_EXECUTOR执行。缺点,稍显繁琐。优点,可以对任务执行进行初始化,执行过程跟踪显示,获取执行结果。
2. 调用静态方法,直接执行一个Runnable对象。Runnable对象被直接放入mTask,提交到THREAD_POOL_EXECUTOR执行。缺点,无法跟踪执行过程,无法获取执行结果。优点,代码简单。适合不关心执行过程和结果的异步任务。

7 AsyncTask的并行执行

我们前面讲过提交到AsyncTask的异步任务,都是放到sDefaultExecutor的任务队列mTask中,sDefaultExecutor顺序的读取mTask中的任务,串行的执行其中的任务。sDefaultExecutor是在Honeycomb版本以后才引入的,在此之前AsyncTask中的任务是并行执行的。
那么如果我们想并行的执行AsyncTask中的任务的时候怎么办?直接以THREAD_POOL_EXECUTOR为参数,调用executeOnExecutor(Executor exec, Params… params)。每次调用executorOnExecutor,任务都将立即被提交THREAD_POOL_EXECUTOR,并得到运行。
后台异步任务的并行执行会引发很多比较难处理的错误。所以Android在Honeycomb以后引入AsyncTask串行执行机制。也建议大家使用AsyncTask的默认的串行执行的行为,如果想要获取对后台任务执行更多的控制力,可以使用Handler+Thread。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值