Android 的系统异步工具类AsyncTask源码解析

由于从wordpress将文章倒回,发现格式有点混乱,经努力调整后依然有部分的格式还未调教好,请多多包涵.

分析AsyncTask或者阐述AsyncTask的博文有很多,本着授人予鱼不如授人予渔的想法我想通过自主学习的方式去探索这个api,如果有阐述不当的地方欢迎各位大神不吝斧正.本文将通过源码学习分析以及demo实例验证的方式彻底的了解AsyncTask运行的原理.

随着开源社区的兴旺,在目前android的开发中有着各种异步的工具以及框架,现在比较热门的当属Rxjava+RxAndroid这个React的开源库,至于这个从Android1.5版本就开始提供的异步工具AsyncTask反而经常出现在Android各类异步实现文章中的反面例子.我写这篇文章有以下的意图:一是通过源码和实例去探讨为什么AsyncTask一直被吐槽.二是了解学习AsyncTask的实现方式(只有了解原理,在使

用中遇到各式问题才有对策).

关于AsyncTask的介绍以及使用例子可以移步官方开发者网站,这里就不多费口舌.AsyncTask的存在的原因是在Android应用运行过程中,耗时的任务不能放在UI线程也就是我们常说的主线程中执行,必须开启线程在后台执行,如果有执行结果要通知到页面上的话需要通过handler进行子线程和UI线程的通信,而AsyncTask就是应运而生去简化这一过程的api.

现在进入实现原理的正题,通过学习源码的过程可以设想如果是要求自己实现一个这样的异步工具应当怎么做,任何创造的前提是从模仿开始.笔者这边使用的sdk version 23的源码.首先是类的定义:

public abstract class AsyncTask {

    private static final String LOG_TAG = "AsyncTask";

    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 ThreadFactory sThreadFactory = new ThreadFactory() {

    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {

         return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());

    }

    };

    private static final BlockingQueue sPoolWorkQueue =new LinkedBlockingQueue(128);

/**

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

AsyncTask是抽象类,根据使用的需要定义了三个泛型分别是传入的参数,执行的Progress,以及执行的结果.通过这一小段的源码可以看到AsyncTask拥有一个静态final的THREAD_POOL_EXECUTOR,这个线程池能够运行的线程最大数是通过 cpu核心数*2+1 计算得出.并且在一个进程中无论开发者写出多少个AsyncTask的实现子类最终管理线程的只是这个大小为128的线程池.如果连续添加超过128个任务,线程池就爆了(这也是被其他开发者诟病的地方,不过这样的开发写法是否符合规范也待商榷).现在接着往下看:

/**

* 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 final int MESSAGE_POST_RESULT = 0x1;

private static final int MESSAGE_POST_PROGRESS = 0x2;

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

private static InternalHandler sHandler;

private final WorkerRunnable mWorker;

private final FutureTask mFuture;

private volatile Status mStatus = Status.PENDING;

private final AtomicBoolean mCancelled = new AtomicBoolean();

private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

private static class SerialExecutor implements Executor {

final ArrayDeque mTasks = new ArrayDeque();

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

}}}

刚才执行的线程池构建好了,现在轮到Exector SERIAL_EXECUTOR,看这个对象的命名为serial,感觉像是单个按顺序的执行器(非并发),我们接着往下看:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

默认的executor为SERIAL_EXECUTOR 并带上了volatile(这里带上volatile类型修饰符的原因是在多线程编程中开发者可以通过调用AsyncTask.setDefaultExecutor(Executor executor)使用自定义的Executor替换SERIAL_EXECUTOR.此处先不急着看SERIAL_EXECUTOR.接着是

private static InternalHandler sHandler;

又是一个静态的对象InternalHandler,贴出类声明:

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;

}

}

}

根据对象名称有internal说明是一个内部的事件handler,构造函数带有super(Looper.getMainLooper())说明是这个Handler的handleMessage(Message msg)的方法是在主线程中调用,分别有两个事件是处理result信息和progress信息这里也就满足了之前类设计需求需要在主线程中反馈执行结果.剩下的

private final WorkerRunnable mWorker;

private final FutureTask mFuture;

private volatile Status mStatus = Status.PENDING;

private final AtomicBoolean mCancelled = new AtomicBoolean();

private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

将放在通过调用类的构造函数以及执行的流程中讲诉.以上我们将AsyncTask的属性分析完毕,接下来通过实例调用过程来进行源码旅程.Demo的例子可以查看Github.

我们创建一个AsyncTask的子类TimeConsumingTask,模拟后台耗时的操作以及反馈结果给UI线程.

public class TimeConsumingTask extends AsyncTask {

private static String TAG = "TimeConsumingTask";

private WeakReference mHandler;

public static final String TAG_RESULT = "result";

private static volatile int sExecutionCount ;

public TimeConsumingTask(Handler handler) {

mHandler = new WeakReference(handler);

}

@Override

protected void onPreExecute() {

Log.d(TAG, "onPreExecute");

}

@Override

protected Boolean doInBackground(Integer... params) {

Log.d(TAG,"execute count is :"+ ++sExecutionCount);

for (Integer num : params) {

try {

Thread.sleep(num * 1000);

//will call onProgressUpdate()

publishProgress(num);

} catch (InterruptedException e) {

e.printStackTrace();

return false;

}}


return true;

}

@Override

protected void onProgressUpdate(Integer... values) {

for (Integer value : values) {

Log.d(TAG, "onProgressUpdate result: " + value);

if (null != mHandler.get()) {

Message message = new Message();

message.what = value;

mHandler.get().sendMessage(message);

}

}

}

@Override

protected void onPostExecute(Boolean aBoolean) {

Log.d(TAG, "onPostExecute result: " + aBoolean);

if (null != mHandler.get()) {

Message message = new Message();

message.getData().putBoolean(TAG_RESULT, aBoolean);

mHandler.get().sendMessage(message);

}}}

这里构造方法我是通过传入一个弱引用的主线程的handler进行运行结果的反馈,虽然只是demo还是希望写的严谨些.子类会率先调用父类的构造方法,此时我们来关注AsyncTask的构造方法,里面包含了刚才未说明的两个属性WorkerRunnable和FutureTask,FutureTask是java1.5引入的api,源码说明是A cancellable asynchronous computation.具体的可以建议读者去好好学习下,当下android很多流行的开源库中实现都离不开FutureTask,这里就不做介绍.我们来看AsyncTask的构造函数:

/**

* Creates a new asynchronous task. This constructor must be invoked on the UI thread.

*/

public AsyncTask() {

mWorker = new WorkerRunnable() {

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

}}};

}

private static abstract class WorkerRunnable implements Callable {

Params[] mParams;

}

private void postResultIfNotInvoked(Result result) {

final boolean wasTaskInvoked = mTaskInvoked.get();

if (!wasTaskInvoked) {

postResult(result);

}}

private Result postResult(Result result) {

@SuppressWarnings("unchecked")

Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,

new AsyncTaskResult(this, result));

message.sendToTarget();

return result;

}

我们可以看到WorkerRunnable是实现Callable在call方法中修改了运行标识|设置线程优先级|以及真正的运行doInBackground(Param)的方法,以及在运行结束后调用postResult(result).其中 Binder.flushPendingCommands();这句的调用可能很多朋友不甚熟悉,可以点击方法跳转至源码查看说明,这里留个悬念.在FutureTask的主要是重写了done()的回调进行如果是未调用后台方法就结束了异步任务的判断,并抛出异常,此处可以发现好的api是在方法命名中就清楚的告诉了阅读者意图.此处的getHandler()就是通过lazyInit的方式获取刚才说到的变量InternalHandler进行通信.

我们看完了构造方法,现在来看AsyncTask的调用方法,AsyncTask的api设计十分简单,可调用方法execute(Params...)/cancel(boolean mayInterruptIfRunning)/...,接下来看下一般继承AsyncTask一般要重写的四个方法:onPreExecute(),doInBackground(Param...), onProgressUpdate(Progress... values) ,onPostExecute(Result result),接下来会讲解这四个方法在源码中被调用的时机.我们先看下核心方法execute(Params...).进入源码:

@MainThread

public final AsyncTask execute(Params... params) {

return executeOnExecutor(sDefaultExecutor, params);

}

@MainThread

public final AsyncTask 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;

}

在方法头加入了google annotation@MainThread,如果还不了解google annotation的朋友也可以上官方开发者网站上搜索进行了解.说明execute方法需要运行在主线程中.execute的方法调用了executeOnExecutor并传入刚才看到的sDefaultExecutor也就是SERIAL_EXECUTOR以及AsyncTask中三个泛型中的第一个Param作为参数.这里在executeOnExecutor运行中对Status进行了检查,如果非PENDING态则抛出异常.Status只有PENDING/RUNNING/FINISHED,三种状态,初始化的时候是PENDING态,这也是我们不能对同一个task反复调用execute的原因.更改状态之后就调用了 onPreExecute();所以开发者继承AsyncTask重写的四大方法中第一个onPreExecute()是运行在主线程中.

exec.execute(mFuture);

AsyncTask的sDefaultExecutor开始执行我们在构造函数中初始化的futureTask了.此处让我们将目光转向sDefaultExecutor的默认赋值SERIAL_EXECUTOR:

private static class SerialExecutor implements Executor {

final ArrayDeque mTasks = new ArrayDeque();

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

好的代码总是简洁明快,通过ArrayDeque 存储传入的任务,并在scheduleNext通过THREAD_POOL_EXECUTOR.execute(task)的方式进行任务调用,这里就不多做阐述了,这个Executor做了和它类名一样的实行就是序列化执行列表中的任务.SERIAL_EXECUTOR是在3.0版本后加入的,说明3.0版本后AsyncTask默认不是并行化执行任务而是顺序执行.也就是说在3.0之前的AsyncTask可以同时有5个任务在执行,而3.0之后的AsyncTask同时只能有1个任务在执行.

之前在文章中已经讲述在WorkerRunnable的call()方法中调用了耗时操作方法doInBackground(Params...),那在后台执行的过程中更新UI的方法onProgressUpdate(Progress... values)又是怎么被调用的,根据官方文档在doInBackground方法中需要post结果到主线程的时候会调用publishProgress(Progress...)方法.而这个方法和主线程有关系我相信读者已经有一个概念该方法中是通过往IntervalHandler发出消息来实现:

@WorkerThread

protected final void publishProgress(Progress... values) {

if (!isCancelled()) {

getHandler().obtainMessage(MESSAGE_POST_PROGRESS,

new AsyncTaskResult(this, values)).sendToTarget();

}

}

这边提一下方法头的googleAnnotation @WorkerThread说明该方法只能在工作线程中被调用.

Demo地址:在使用Demo时候可以通过DDMS的Thread监控功能进行AsyncTask的线程池监控验证.

这里只是抛砖引玉写一篇AsyncTask的源码分析,希望各位大神能热心参与android异步编程的讨论.

Android异步一些轻量实现的讨论文章:https://medium.com/@ali.muzaffar/handlerthreads-and-why-you-should-be-using-them-in-your-android-apps-dc8bf1540341#.6de7zdbii

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

推荐阅读更多精彩内容