整天用AsyncTask,但它的内部原理一直没有特意去研究,今天趁着有时间,码下它的原理。

  具体用法就不再说明,相信大家已经用得很熟练了,我们今天就从它怎么运行开始说。先新建好我们的AsyncTask:

 class MyAsyncTask extends AsyncTask<String,Integer,Boolean>{

     @Override
protected Boolean doInBackground(String... voids) {
return true;
}
}

  好了,一个最基本的代码已经好了,接下来我们怎么运行它呢?当然,大家都知道的。

new MyAsyncTask().execute("参数");

  那平时做到这里,我们可能已经不去往不管了,因为已经执行起来了,但是,你有没有想过

  • 为什么调用execute()就可以运行了
  • 为什么doInBackground()方法里面不能操作UI,而onPostExecute就可以

  等等一连串的问题,接下来,我们就一步一步的去看一下,它内部到底做了哪些。

  首先,我们看到它new了一个AsyncTask对象,那我们就去它构建函数看看。

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

  看着很多,其它就是new了两个对象mWorker和mFuture,其实它们分别是Callable和Future的实现,关于它们两个的实现原理,我会在另外一篇文章里讲解。需要注意的是,在实例化mFuture时,把mWorker当作参数传入。现在还没有用到它们,我们接下来往下看execute():

     @MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
} @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; onPreExecute(); mWorker.mParams = params;
exec.execute(mFuture); return this;
}

  我们看到了onPreExecute(),这也解释了它确实比doInBackground运行的早,可以做一些准备工作。接下来,用到了我们前面提到的两个对象,这里是把我们传递过去的参数赋给了mWorker的变量,然后看26行 调用了Executor的execute(),并且把mFuture做为参数传递过去了,那我们看上一个方法,是把sDefaultExecutor当作参数传递过来的,那就看下它的定义:

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

  它的最后实现类是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的方法execute()方法传入的参数Runnable,其实就是我们前面的mFuture(FutureTask实现了Future和Runnable),execute()方法内,会把我们传入的mFuture存入mTasks,这时mActive还等于null,所以会执行scheduleNext(),这样就会从队列中取出第一个,放入线程池中,开始执行,当前也就是我们刚刚放入的mFuture,也就是执行了mFuture的run方法,至于run方法内部怎么运行的,参见我的另一篇文章:Runnable和Future的原理 。总之呢,它就运行了mWorker的call(),为了方便理解,就把mWorker的call()代码从上面copy到这里
             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);
}

  我们看到,4行修改了线程的优先级,第6行就有我们熟悉的doInBackground(),结果赋给了result,现在是在另外一个线程里面,所以,在doInBackground方法里面是不能操作UI的。最后返回的时候是调用了postResult(),并把结果当参数传入。

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

  这里面就是我们熟悉的Handler了,查找Handler最后的实现。

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

  根据我们发送消息的CODE 为 MESSAGE_POST_RESULT,我们发现执行的是第13行,仔细看代码会发现,result.mTask其它就是我们的MyAsyncTask对象,调用它的finish方法

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

  这就一目了然了,最后会调用我们的onPostExecute方法,并把结果传过去。基本的调用流程就是这样的。

  如果你仔细看了,你可能会发现,mFuture实例化的时候,重写的done()方法并没有介绍到,那它在整个流程中处于什么样的作用呢,我们接下来,单独说一下。

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

  根据名字应该可以猜到,这个方法是在整个异步任务执行完后调用的,在Future中,通过get()就可以获取到我们前面说的mWorker中call方法的返回值(具体原理可看我另一篇文章),那我们再看postResultIfNotInvoked方法,到底对返回的结果做了哪些处理。

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

  我们看到了postResult(),这个方法,我们在上面刚刚讲过,它是在mWorker的call方法中,返回的时候调用的,你可能会问了,这个方法怎么调用了两次,不慌,我们看上面这个方法的名字,就了了解原因了post result if not invoked ,很明显,这处的调用是防止call()里面没有调用而设置的,开关就是第2行的mTaskInvoked,它是一个AtomicBoolean,具体它干嘛的,自行google,我们发现在call方法里面,有这样一段代码

mTaskInvoked.set(true);

  所以,在postResultIfNotInvoked方法里面时,如果执行了call方法,这里就会返回true,下面就无法执行,这样就保证了postResult方法只执行了一次。

  

												

通过源码看android系列之AsyncTask的更多相关文章

  1. 通过源码看android系列之multidex库

    我们在开发项目时,喜欢引入好多的第三方包,大大的方便了我们的开发,但同时,因为android方法总数的限制,不能超过65k,然而呢,随着我们的开发,65k最终还是会超过,所以,google就给出了这个 ...

  2. 通过源码看原理之 selenium

    # selenium的历史1. selenium1.x:这个时候的selenium,使用的是JavaScript注入技术与浏览器打交道,需要Selenium RC启动一个Server,将操作Web元素 ...

  3. Kafka详解六:Kafka如何通过源码实现监控

    问题导读: 1.kafka的消费者组的消费偏移存储,kafka支持两个版本?        2.ConsumerOffsetChecker类的作用是什么?        3.Kafka如何通过源码实现 ...

  4. 通过源码安装PostgresSQL

    通过源码安装PostgresSQL 1.1 下载源码包环境: Centos6.8 64位 yum -y install bison flex readline-devel zlib-devel yum ...

  5. 通过源码了解ASP.NET MVC 几种Filter的执行过程

    一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...

  6. 追源索骥:透过源码看懂Flink核心框架的执行流程

    li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt, ...

  7. 通过源码了解ASP.NET MVC 几种Filter的执行过程 在Winform中菜单动态添加“最近使用文件”

    通过源码了解ASP.NET MVC 几种Filter的执行过程   一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神 ...

  8. Linux下通过源码编译安装程序

    本文简单的记录了下,在linux下如何通过源码安装程序,以及相关的知识.(大神勿喷^_^) 一.程序的组成部分 Linux下程序大都是由以下几部分组成: 二进制文件:也就是可以运行的程序文件 库文件: ...

  9. 在centos6.7通过源码安装python3.6.7报错“zipimport.ZipImportError: can't decompress data; zlib not available”

    在centos6.7通过源码安装python3.6.7报错: zipimport.ZipImportError: can't decompress data; zlib not available 从 ...

随机推荐

  1. ASP.NET State Service服务

    ASP.NET State Service服务是用来管理 Session 的,正常来说,Session 位于IIS进程中(其实可以理解成在服务器的内存中),当IIS重启或程序池回收会自动清空Sessi ...

  2. Waterfall———瀑布流布局插件, 类似于 Pinterest、花瓣、发现啦。

    瀑布流布局插件, 类似于 Pinterest.花瓣.发现啦. En 中文 文档 下载 下载waterfall插件最新版本. 使用 html: <div id="container&qu ...

  3. 把内表 itab1 的 n1 到 n2 行内容附加到 itab2 内表中去.

    语法:append lines of itab1 [ from n1 ] [ to n2 ] to itab2. DATA:BEGIN OF gt_00 OCCURS 0,        l_01   ...

  4. Android Environment 判断sd卡是否挂载 获取sd卡目录

    在将一个文件存储到sd卡上面的时候,一般需要判断sd是否已经挂载才进行操作. 那么如何判断sd卡已经挂载呢? 我们可以使用Android的Environment类,具体使用如下: if(Environ ...

  5. CF 33B String Problem

    对每个位置进行操作,求出最终变成相同字母的代价,然后把所有的位上代价加起来,使得总代价最小.res[i][j]表示将字母i+'a'和字母j+'a'变为相同的代价,设最终都变成字母k+'a',那么res ...

  6. redis pub/sub 发布订阅

    Redis的列表数据结构有blpop和brpop命令,能从列表里返回且删除第一个(或最后一个)元素,或者被堵塞,直到有一个元素可供操作.这可以用来实现一个简单的队列.(参考:http://www.cn ...

  7. Python 几个重要的内置函数

    所谓内置函数,就是在Python中被自动加载的函数,任何时候都可以用.内置函数,这意味着我们不必为了使用该函数而导入模块.不必做任何操作,Python 就可识别内置函数.在学习Python的过程中,有 ...

  8. UVA_1025_A_Spy_in_the_Metro_(动态规划)

    描述 https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&a ...

  9. 通过Microsoft Azure服务设计网络架构的经验分享

    作者 王枫  发布于 2014年4月8日 本文从产品设计和架构角度分享了Microsoft Azure网络服务方面的使用经验,希望你在阅读本文之后能够了解这些服务之间,从而更好地设计你的架构. Mic ...

  10. ELK安装配置及nginx日志分析

    一.ELK简介1.组成ELK是Elasticsearch.Logstash.Kibana三个开源软件的组合.在实时数据检索和分析场合,三者通常是配合使用,而且又都先后归于 Elastic.co 公司名 ...