码迷,mamicode.com
首页 > 移动开发 > 详细

Android AsyncTask工作原理

时间:2016-10-21 11:22:41      阅读:341      评论:0      收藏:0      [点我收藏+]

标签:ota   抛出异常   ddc   stat   最大   进度条   handle   spool   ble   

AsyncTask能够适当简单的使用在UI线程,在没有任务线程和handler的情况下,这个类也允许执行后台操作并将结果显示在UI线程上。

AsyncTask的引入,我们在执行完耗时的后台任务后可以很方便的更新UI元素。相信大多数同学对AsyncTask的用法都已经很熟悉,那这里不在叙述他的基本用法了。

AsyncTask的实现原理是封装线程池和消息机制,我已经在自己的博客写过了线程池和消息机制的。感兴趣的同学可以去阅读下。

那么直接进入AsyncTask的源码分析,一起从源码的角度彻底理解。

先从AsyncTask的构造方法看起

/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 */
public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            Result result = doInBackground(mParams);
            Binder.flushPendingCommands();
            return postResult(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);
            }
        }
    };
}

从方法的注释我们知道,创建一个异步任务,这个构造方法必须要在UI线程中调用。
AsyncTask的构造方法中,首先创建了一个WorkerRunnable对象并赋值给mWorker,那么这个WorkerRunnable是什么呢?

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

从上面的代码中可知道WorkerRunnable其实就是Callable。接着又创建了一个FutureTask对象,并将mWorker传进。AsyncTask的构造方法只是做了初始化的工作。
关于Callable和FutureTask我已经在线程池系列的博客中介绍过了。简单说就是FutureTask是Runnable的实现类,其中封装了Callable,当FutureTask的run方法被调用时,内部实际调用的是Callable的call方法。那么我们等下就寻找FutureTask的run方法在哪里被调用了, doInBackground方法也就是在那里开始被调用的。

那么接下来就是跟踪AsyncTask的 execute方法了,源码如下:

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

内部调用了executeOnExecutor方法,这个方法顾名思义就是在线程池中执行,传入sDefaultExecutor和在参数params。
那么sDefaultExecutor又是什么呢?先不管。
我觉的刚开始分析execute方法不要一直在研究从四面八方出现的变量。因为让我们感到莫名其妙出现的变量实在太多了,我们一直去跟踪思路很容易会散了。稍后会分析sDefaultExecutor。
那么继续跟进executeOnExecutor方法看看:

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

关于Status是一个枚举类型,他有三种状态:PENDING,RUNNING, FINISHED,分别标明了当前AsyncTask的状态。当然初始化的时候是PENDING准备就绪状态,从

private volatile Status mStatus = Status.PENDING;

这行代码也可以看出。那么继续往下看这个executeOnExecutor方法,之后会将Status的状态设置为RUNNING正在运行状态。AsyncTask如果在RUNNING, FINISHED状态执行executeOnExecutor方法就会抛出异常了,也表明一个AsyncTask实例只能执行一次任务。接着就会回调onPreExecute方法,所以我们可以重写onPreExecute这个方法做一些准备工作。

好,重头戏要开始了。

exec.execute(mFuture);

exec调用了execute方法并将mFuture(FutureTask对象)传入,之前我们已经在构造方法中初始化了FutureTask对象并赋值给mFuture。不过还需要知道exec,回到最初,我们是在AsyncTask的execute方法里,将sDefaultExecutor传了进来executeOnExecutor方法,所以exec就是sDefaultExecutor。
那么就需要知道sDefaultExecutor的execute方法里的内部实现是怎么样的,才能解开我们心中的疑惑了。
现在才是搞明白sDefaultExecutor是什么的正确时机,那我们就先来瞧瞧sDefaultExecutor是什么先?

/**
 * 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;

由此可知道,sDefaultExecutor是SerialExecutor的实例,从名字可以看出SerialExecutor是一个串行执行任务的线程池。我在Android中的线程池(二)那篇博客中也详细讲了各种线程池。

那就来看看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确实是一个线程池,很重要。这段代码也是我觉得最不好理解的。里面实现了Executor 接口的execute方法。
SerialExecutor的execute方法内部,首先向ArrayDeque队列中提交一个Runnable对象,在其run方法里调用r的run方法,r就是mFuture。

public void run() {
    try {
        r.run();//等价于mFuture.run()
    } finally {
        scheduleNext();
    }
}

有点绕,但容易理解的。其实就是这个run方法运行时,就调用FutureTask的run方法嘛。

当第一次执行execute方法,mActive一定是为空的,那么就会通过if (mActive == null)的判断调用到scheduleNext方法。
可以看到scheduleNext这个方法从队列的头部取出一个元素并赋值给mActive,如果mActive不为空,即队列里有任务。就调用THREAD_POOL_EXECUTOR的execute方法将mActive传进去执行任务。mActive经过第一次赋值之后就不为空了。之后就会进入finally块。也就是说scheduleNext还是会被调用。这样后续的任务又得到了处理。

总结一下:可以这样理解,假设我有10个AsyncTask任务,全部进入队列,肯定会有其中一个AsyncTask任务是第一个执行的吧,当第一个AsyncTask任务执行完毕,就会进入finally块,调用scheduleNext方法继续取出队列中的任务,这样下一个任务又得到了处理。
只有当一个任务执行完毕了,下一个任务才会执行。所以为什么说这种线程池是串行执行任务。

AsyncTask有两个线程池的,SerialExecutor和THREAD_POOL_EXECUTOR。前者负责任务的排队,后者用于任务的执行。THREAD_POOL_EXECUTOR的配置如下:

/**
 * An {@link Executor} that can be used to execute tasks in parallel.
 */
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

这些核心线程数,最大线程数,任务队列等参数如下:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

那我们跟进THREAD_POOL_EXECUTOR的execute方法,看看是怎样执行任务的。

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

这里的流程,我也在之前的博客分析过了,不过这里为了更好阅读,我还是在分析一遍吧。这个execute方法里的逻辑不简单,所以我们要结合AsyncTask使用到的线程池的参数配置,抓主要信息阅读。

那主要是将mFuture加入将要执行的队列中,我们只需要跟进addWorker方法即可。

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:

    //代码省略

    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
                    //代码省略
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

创建一个Worker对象并对传进的FutureTask进行包装,从Worker对象中取出线程并赋值给t,然后将Worker对象添加进工作线程队列等待执行,最后线程t调用start方法。即调用Worker里的run方法,在Worker里的run方法里又调用了runWorker方法,如下:

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
               //代码省略
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } 
        //代码省略
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

取出刚才在Worker对象中封装的FutureTask对象,并赋值给task,最后task调用run方法。这样mFuture的run方法就得到了调用。

在FutureTask对象run方法里会调用Callable的call方法,FutureTask对象run方法如下:

public void run() {
    //代码省略
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            }
    //代码省略
}

这个Callable也就是我们最初在AsyncTask构造方法中创建的WorkerRunnable对象mWorker。现在我们可以看到mWorker的call方法了

public Result call() throws Exception {
    mTaskInvoked.set(true);

    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    //noinspection unchecked
    Result result = doInBackground(mParams);
    Binder.flushPendingCommands();
    return postResult(result);
}

饶了地球一圈,终于执行到了doInBackground方法了。这里也可以证实,onPreExecute方法是第一个被调用的,而第二个回调的是doInBackground方法。
那继续前进吧,将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;
}

终于看到消息机制了,getHandler方法获取到InternalHandler对象,并将MESSAGE_POST_RESULT字段和一个包装了result的AsyncTaskResult对象发送给handleMessage方法接收。

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

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

AsyncTaskResult是一个用于封装AsyncTask实例和结果集的内部类。

那么我们现在直接到handleMessage方法一看究竟。

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

在InternalHandler的构造方法中,获取到UI线程的Looper对象,这就是为什么AsyncTask要在主线程创建,因为我要关联主线程的Looper,消息队列。

刚才发的消息是MESSAGE_POST_RESULT,那我们看到这个分支即可,这里调用了AsyncTask的finish方法并传进result。

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

在一路顺畅不抛异常的正常情况下,AsyncTask没有被取消,会将result传入onPostExecute方法,执行完onPostExecute后,整个AsyncTask的生命周期也就结束了。

还有一个知识点没讲到的是,在doInBackground方法中调用publishProgress方法,可以在onProgressUpdate方法中更新UI,比如更新进度条的进度。

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

其实这个publishProgress方法也很简单,就是将当前的进度值values发送到主线程。

如果我们想要并行执行任务,可以调用AsyncTask的executeOnExecutor方法。如下:

new AsyncTask<Void,Void,Void>(){

    @Override
    protected Void doInBackground(Void... params) {
        return null;
    }

}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,null);

其实很好理解的。直接执行executeOnExecutor方法,传入AsyncTask.THREAD_POOL_EXECUTOR线程池。这样就绕过了SerialExecutor任务排队的过程。

以上就是根据源码的角度分析的AsyncTask的工作原理。

Android AsyncTask工作原理

标签:ota   抛出异常   ddc   stat   最大   进度条   handle   spool   ble   

原文地址:http://blog.csdn.net/xyh269/article/details/52605216

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!