AsyncTask源码解析

AsyncTask是android为我们提供执行异步任务的一个轻量的类,可以用来处理耗时操作,并且能够很方便的将执行结果返回给主线程。本篇文章将会通过源码分析来介绍AsyncTask的内部实现原理。

目录

  1. 重要的成员变量
  2. AsyncTask构造分析
  3. 两个线程池
  4. 图解AsyncTask执行过程
  5. 执行结果是如何被传递到主线程
  6. onProgressUpdate()是什么时候调用

1. 重要的成员变量

AsyncTask里面几个重要的成员变量变量分别为:

名称 作用 创建 调用 备注
THREAD_POOL_EXECUTOR 真正执行任务的线程池 在静态代码块中被创建 在SerialExecutor线程池的scheduleNext方法中被调用 该线程成池的核心线程数量是根据手机cup核数-1确定的
sDefaultExecutor 内部创建队列用于储存异步任务 创建类的成员变量的时候被创建 在AsyncTask的execute()中被作为参数传递 SerialExecutor类的scheduleNext方法中会将任务添加到THREAD_POOL_EXECUTOR线程池中执行
mWorker 任务最终执行方法,其内部的call方法会调用doInBackground()方法 在AsyncTask有参构造中创建 WorkerRunnable在FutureTask的run方法中被调用该类的call方法 其继承自Callable方法,一般配合FutureTask使用
mFuture 在其内部会调用mWorker的call方法来执行任务 在AsyncTask有参构造中创建 FutureTask在SerialExecutor类的execute方法中被调用 该成员变量被AsyncTask的executeOnExecutor()中传递到SerialExecutor中
sHandler 用于将在结果返回到主线程 在AsyncTask有参构造中通过调用getMainHandler来创建 在postResult()中通过复用Message来调用 InternalHandler类的Looper是主线程的Looper

2. AsyncTask构造分析

在分析AsyncTask之前我们先看看他的构造,我们在使用AsyncTask经常使用空参构造的方式来创建该对象,这个构造方法内部会调用他的有参构造。首先有参会先根据是否有Looper来创建Handler。如果传入的Looper为空或者传入的Looper不是主线程的Looper,则调用getMainHandler()来创建Handler;如果是主线程的Looper则以此Looper重新new一个Handler。当Handler创建完毕后然后在以次创建WorkerRunnableFutureTask。下面为AsyncTask构造源码:

public AsyncTask(@Nullable Looper callbackLooper) {
    //创建Hanlder
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Result result = null;
            try {
                //将进程设置成标准后台进程
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //获取异步执行结果
                result = doInBackground(mParams);
                //将进程中未执行的命令一并送往cup处理
                Binder.flushPendingCommands();
            } catch (Throwable tr) {
                mCancelled.set(true);
                throw tr;
            } finally {
                //将处理结果返回到主线程
                postResult(result);
            }
            return result;
        }
    };
    //FutureTask间接调用了WorkerRunnable方法的call方法
    //来获取执行结果
    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);
            }
        }
    };
}

从源码中我们可以知道,mHandler实际上是InternalHandler,mWorker内部的call()方法会调用doInBackground,try块不管执行结果如何,都会调用postResult()来调用Hanlder发送消息,通知主线程最Ui更新操作。先有一个问题,call()方法是在哪里会被调用呢?其实是在mFuture内部的run()方法中调用mWorker他的call方法。具体代码读者可以自行查找项目源码,这里就不多说了。上面提到的mWorker、mFuture会在execute()方法中被调用和传递,execute()是用于配置和启动任务的方法,下面为该方法的部分代码。

/**
*在主线程中执行
*可传入一个或多个参数
*/
@MainThread
public final AsyncTask。<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

3. 两个线程池

executeOnExecutor(sDefaultExecutor, params);方法将参数params和sDefaultExecutor传入该方法中,并返回一个AsyncTask。这个params我们知道它是我们传进来的参数,但是sDefaultExecutor是什么呢?它是一个线程池,是一个类的成员变量。

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

既然我们知道sDefaultExecutor是一个线程池,也就是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() {
        //mTask.pll()删除队列中的第一个元素,并返回该元素的值
        if ((mActive = mTasks.poll()) != null) {
            //调用线程池执行异步
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

从上面的代码我们可以知道,SerialExecutor类中创建一个双端队列ArrayDeque,
用于储存异步任务。他还有execute()scheduleNext()方法,execute()内部调用了mTasks.offer用于将传入的异步任务添加到队列中,然后在调用 scheduleNext()方法。scheduleNext()方法调用mTask.poll()方法取出并删除第一个元素,最后将取出的元素放到线程池中。不知道读者有没有发现AsyncTask内部其实是有两个线程池SerialExecutorTHREAD_POOL_EXECUTOR,其中SerialExecutor线程池主要是用于将任务添加到队列中,而任务真正的执行是在THREAD_POOL_EXECUTOR线程池中。

4. 图解AsyncTask执行过程

要想知道执行结果是如何被传递到线程中,我们先搞明白AsyncTask的执行过程。其实读者从上面的内容中或许能改猜到它的大概执行过程。其实它的执行过程也不复杂我们可以结果下面这张图进行分析:

AsyncTask执行流程图

我们在使用AsyncTask的时候会先创建对象,然后调用execute()方法传入参数执行任务:

//创建AcyncTask封装类
TestAsyncTask asyncTask = new TestAsyncTask();
//传入参数,执行任务
asyncTask.execute(5,6,7);

我们在通过上面操作执行任务的时候,其实AsyncTask内部做了一下几个操作:

  1. 在构造中创建Handler、WorkerRunnable、FutureTask
  2. executeOnExecutor()中校验该任务是否在任务栈中执行、或者是否已完成过
  3. 如果该未任务在执行,或者未完成过。将会包装传入的参数然后再将FutureTask添加到线程池中调用execute()方法执行异步
  4. SerialExecutor线程池的execute()方法创建Runnable,并添加到队列中。
  5. scheduleNext()方法取出队列中的第一个Runnable,加他添加到THREAD_POOL_EXECUTOR线程池中开始执行任务
  6. Runnable调用FutureTask的run()方法执行WorkerRunnable的call()方法
  7. WorkerRunnable的call()方法执行完,SerialExecutor线程池的execute()方法再次调用scheduleNext()执行下个任务。

结合上面的执行流程图我们知道,在经过上面7个步骤异步任务一个一个的在线程池中被完成。既然我们知道了AsyncTask的大致执行过程,那么它是如何将执行结果返回到主线程呢?下面我们将会来分析。

5. 执行结果是如何被传递到主线程

我们知道doInBackground()函数是我们的任务具体执行函数。这个函数是在WorkerRunnable的call()函数中被调用,从上面的执行过程介绍中我们知道call()方法是在FutureTask的run方法执行的时候被调用的。当call()方法在执行完doInBackground()方法得到结果后,会将该结果传递给postResult()方法:

private Result postResult(Result result) {
    //obtainMessage方法是从Message池中获取一个Message对象,避免重复创建。
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    //发送消息
    message.sendToTarget();
    return result;
}

postResult()方法内代码也很简单,首先它会通过Hanlder(注:从文章开始部分我们可以知道,这个Handler的Looper是主线程的Looper)在消息队列中获取一个Message对象,然后将结果和定义的标记包装到Massage中,最后在通过Message对象调用sendToTarget()将消息发出。既然消息发送出去了,那么消息是在哪里执行呢?答案是:在InternalHandler类中的handleMessage()中被执行。why?因为getHandler()获取的是Hanlder是我们在文章开始介绍的构造函数中被getMainHandler()赋值的mHandler,而getMainHandler()中返回的就是InternalHandler。既然我们知道了消息在哪里被处理,那么我们可以看一看它的具体处理逻辑:

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

handleMessage()内部有两个判断,如果标识是MESSAGE_POST_RESULT则将结果传递给finish()方法。如果标识是MESSAGE_POST_PROGRESS则调用onProgressUpdate()用于更新进度。下面我们先看finish()方法的源码:


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

finish()方法会判断是否取消了该任务,如果用户调用了cancel()函数那么isCancelled()返回的就是true,当用户取消了任务那么将会回调onCancelled(result)函数而onPostExecute()则不会调用,反之则会调用。

6. onProgressUpdate()是什么时候调用

在分析handleMessage()方法的时候我们留了一个小尾巴,MESSAGE_POST_PROGRESS这个标记消息在什么时候发出的?在回答这个问题之前,我们先回忆一下我们在使用doInBackground()的时候,是否有在其内部调用publishProgress()函数来更新进入?回忆到这里答案就很明显了:通过Handler发生更新进度消息的操作是在publishProgress()函数中完成的。下面为该函数的源码:

@WorkerThread
protected final void publishProgress(Progress... values) {
    //如果任务没有取消,则发生消息更新进度
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

从上面的源码我们可以知道,更新进度的消息是在子线程中发送的,如果该任务没有被取消那么就可以发现消息。这种通过复用Message对象发送信息的方式对性能上有起到优化的作用。读者可以在文章结尾的参考链接中找到相关的介绍,笔者就不介绍了。

总结

文章到这里对与AsyncTask的源码分析也就介绍完了。在AsyncTask中比较重要的成员变量为:WorkerRunnable、FutureTask已经两个线程池,能够真正理解AsyncTask的执行过程一定要搞明白他们几个的调用过程。最后感谢您能在百忙之中抽出时间阅读这篇文章,下一篇文章将会写一下HandlerThead和IntentService。

参考

ArrayDeque类的使用详解

Android线程优先级

剖析Android中进程与线程调度之nice

带你轻松看源码---AsyncTask(异步任务)

Android Message和obtainMessage的区别

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容