本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢

博客地址:http://blog.csdn.net/l540675759/article/details/62893318

写作背景

  • 愚人节特别篇

  • 这篇博客准备了好久,一直放在草稿箱里面,随着之前的深入了解Android线程池 . 完结之后,在写这篇文章就顺手了非常多.

  • 写这篇博客的时候,仅仅想了解下AsyncTask,没想到看着源代码之后就沉迷了,一点一点看了队列,同步,异步,也尝试着翻译英文文档,总之收货满满.

  • 本文用图,所有为本人独立创作,假设转载请注明出处,请尊重原创谢谢.

  • 本文大部分观点,属于博主个人观点,如有错误,希望大家多多指正,然后博主及时更改.

  • 最后希望看到博主文章的小伙伴,能够给博主顶一顶博客,欢迎留言,欢迎讨论.

前言:

  • 本篇文章主要介绍AsyncTask的内部工作原理

  • 在阅读文本之前,假设你不太了解AsyncTask,能够先參考我的博客AsyncTask(异步任务)分析之基本使用

  • 文中涉及到一些术语如:串行、并行、队列这些关键词会在导读中为大家介绍


导读:

AsyncTask的组成非常easy。可是还有有一些关键的知识点,导读中将会把一些比較重要的概念整理出来。让大家提前了解下,会使接下来的阅读更顺畅。

串行和并行的概念

在Android3.0之前,AsyncTask一直是并行运行的。而在Android3.0之后更改为串行运行,假设对串行和并行概念不太了解。能够參阅下我之前的线程中同步、异步、串行、并行这篇文章,通过图能让你迅速了解串行和并行的基本概念。


Android的消息机制,Handler的原理

在AsyncTask的工作原理中,InternalHandler是其运转的主要一环。AsyncTask通过它,来保持和UI线程的通信。从而实现进度的更新,以及AsyncTask的回调、中断的通知。

假设对Handler机制不太熟悉。能够參考下android的消息机制——Handler机制这篇文章。


队列的概念。容器ArrayDeque的使用

在AsyncTask中,有一个双端队列的线程池。通过其来保持AsyncTask的调度,而其内部的容器就是ArrayDeque。对于ArrayDeque网上的介绍比較少,这里能够參考我整理的深入了解双端队列Deque能够大致了解ArrayDeque的结构。


Android中的线程池

AsyncTask中採用线程池用于任务的调度和运行,假设对线程池不太了解,能够參考我的博文深入了解Android线程池 .


AsyncTask的源代码解析

AsyncTask的主要成员

从AsyncTask的内部模块图能够看出,AsyncTask主要分为下面几个模块 :

(1)用于任务的调度的线程池SerialExecutor,图中的任务队列池.
(2)用于任务的运行的线程池ThreadPoolExecutor,图中的运行任务的线程池.
(3)用于和UI线程交互的InternalHandler,通知任务的完毕的进度,以及任务的一些状态.

AsyncTask的状态

    public enum Status {
/**
* AsyncTask的初始状态,表明AsyncTask处于未运行任务的状态
*/
PENDING,
/**
* AsyncTask的运行状态,表明AsyncTask正在运行任务,正在运行
*/
RUNNING,
/**
* AsycnTask的完毕状态,表明AsyncTask处于完毕状态,而且会调用onPostExecute
*/
FINISHED,
}

任务调度线程池SerialExecutor

我们能够看到SerialExecutor仅仅是实现了Executor这个接口,其内部维持的队列是ArrayDeque(一种双向队列).它会在一个任务运行结束,和SerialExecutor初始化之后,会自己主动去队列中获取任务并通过THREAD_POOL_EXECUTOR(运行线程池)运行.

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

任务的运行的线程池THREAD_POOL_EXECUTOR

运行线程池的初始化过程:

    //获得当前CPU的核心数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//设置线程池的核心线程数2-4之间,可是取决于CPU核数
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
//设置线程池的最大线程数为 CPU核数*2+1
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//设置线程池空暇线程存活时间30s
private static final int KEEP_ALIVE_SECONDS = 30; //初始化线程工厂
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());
}
}; //初始化存储任务的队列为LinkedBlockingQueue 最大容量为128
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128); /**
* 一个能够运行并行任务的线程池哦
*/
public static final Executor THREAD_POOL_EXECUTOR; static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
//设置核心线程池的 超时时间也为30s
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}

用于和UI交互的InternalHandler

    private static class InternalHandler extends Handler {

        //注意:这里是获取主线程的Looper(),也就是为什么AsyncTask能和主线程交互的原因
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:
//这里将结果通过Handler传递到主线程
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
//这是是通知主线程更新进度的操作.
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}

AsyncTask的内部模块图

上图是AsyncTask的内部模块图。通过此图大家能够了解AsyncTask简单的工作原理。


AsyncTask的构造方法

public AsyncTask() {
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);
//将进程中未运行的命令,一并送往CPU处理
Binder.flushPendingCommands();
} catch (Throwable tr) {
//假设运行异常,设置取消的标志
mCancelled.set(true);
throw tr;
} finally {
//发送结果
postResult(result);
}
return result;
}
}; //一个包装任务的包装类
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
//在运行完任务做一道检查,将没被调用的Result也一并发出.
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) {
//假设发生异常,则将结果滞null发出.
postResultIfNotInvoked(null);
}
}
};
}

在AsyncTask的构造方法中,将任务包装好,然后进行初始化操作:

在构造方法中,WorkerRunnable就一个能储存參数的Callable.

//这里的Callable也是任务,可是与Runnable不同的是,Callable<T>存在返回值,返回值就为其泛型
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}

而FutureTask也是一个包装类,我们来看下FutureTask的构造方法:

//事实上FutureTask内部包括Callable<T>,而且添加了一些状态标识和暴漏出操作Callable<T>的一些接口.
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}

而其done()方法,就是FutureTask内的Callable运行完毕之后的调用方法.在done()方法中,对任务的调用进行复查,将未被调用的任务的结果通过InternalHandler传递到UI线程.

//方法非常easy,就是取得标志,然后推断该标志而已,复查没有被调用的任务,将其Result对象发送出去.
private void postResultIfNotInvoked()(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
//假设没有被运行,也须要把结果发送出去.
if (!wasTaskInvoked) {
postResult(result);
}
}

AsyncTask的工作原理

通过上面的介绍,我们了解了AsyncTask的核心组成,以及AsyncTask在初始化所做的操作.

execute()

那么接下来,我们来一起看下AsyncTask的工作原理,这里我们从AsyncTask的运行方法execute()開始:

    @MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
//从这里我们发现终于调用的方法是executeOnExecutor()方法
//此时是通过队列线程池储存任务,然后运行线程池取出任务运行.
return executeOnExecutor(sDefaultExecutor, params);
}

executeOnExecutor()

   @MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
//推断AsyncTask当前的运行状态,PENDING为初始化状态
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)");
}
}
//executeOnExecutor()被调用时,AsyncTask的状态就变成了RUNNING状态
mStatus = Status.RUNNING;
//此时进行准备工作(主线程)
onPreExecute();
//将參数加入到任务中
mWorker.mParams = params;
//运行任务
exec.execute(mFuture);
return this;
}

AsyncTask的基本运行流程大致的情况,上面已经介绍,须要注意的是,在Android3.0之前AsyncTask是并行运行的.

而在Android3.0之后默认AsyncTask是串行运行的.关于线程的串行和并行这里假设不明确,能够參考导读.详细大家能够在Android模拟器分别在3.0下面和3.0以上的版本号进行測试.

而假设在Android3.0以上的版本号并行运行AsyncTask,我们能够这样:

    @Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Log.d(TAG, "异步任务完毕阶段阶段");
SimpleDateFormat df =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Log.d(TAG, "当前异步任务 : "+"\t"+ this.hashCode()+"" );
Log.d(TAG, "当前结束时间 : "+"\t"+df.format(new Date()) );
}
//这里直接调用executeOnExecutor()方法,而且制定处理的线程池为 //AsyncTask.THREAD_POOL_EXECUTOR,也就是运行线程池
new
TestAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
new TestAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
new TestAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
new TestAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");

通过在完毕方法打印当前的AsyncTask的hashCode来区分是不是同一个AsyncTask,以及时间上的差异来确定是否是并行,得到的结果为:

03-31 13:13:48.505 7050-7050/com.commonproject.debug D/TestAsyncTask: 当前异步任务 :  632327564
03-31 13:13:48.505 7050-7050/com.commonproject.debug D/TestAsyncTask: 当前结束时间 : 2017-03-31 13:13:48
03-31 13:13:48.505 7050-7050/com.commonproject.debug D/TestAsyncTask: 异步任务完毕阶段阶段
03-31 13:13:48.506 7050-7050/com.commonproject.debug D/TestAsyncTask: 当前异步任务 : 496311509
03-31 13:13:48.506 7050-7050/com.commonproject.debug D/TestAsyncTask: 当前结束时间 : 2017-03-31 13:13:48
03-31 13:13:48.507 7050-7050/com.commonproject.debug D/TestAsyncTask: 异步任务完毕阶段阶段
03-31 13:13:48.507 7050-7050/com.commonproject.debug D/TestAsyncTask: 当前异步任务 : 688151786
03-31 13:13:48.507 7050-7050/com.commonproject.debug D/TestAsyncTask: 当前结束时间 : 2017-03-31 13:13:48
03-31 13:13:48.508 7050-7050/com.commonproject.debug D/TestAsyncTask: 异步任务完毕阶段阶段
03-31 13:13:48.508 7050-7050/com.commonproject.debug D/TestAsyncTask: 当前异步任务 : 304548827
03-31 13:13:48.508 7050-7050/com.commonproject.debug D/TestAsyncTask: 当前结束时间 : 2017-03-31 13:13:48

由上述结果能够发现,上述的并行的运行过程直接绕过了队列线程池,直接制定运行线程池去运行任务.那么并行的原因在哪呢 ?

public AsyncTask() {
......
try {
//设置线程的优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//运行异步操作
result = doInBackground(mParams);
//将进程中未运行的命令,一并送往CPU处理
Binder.flushPendingCommands();
.........

注意Binder.flushPendingCommands()这个JNI命令.

    /**
* Flush any Binder commands pending in the current thread to the kernel
* driver. This can be
* useful to call before performing an operation that may block for a long
* time, to ensure that any pending object references have been released
* in order to prevent the process from holding on to objects longer than
* it needs to.
*/
public static final native void flushPendingCommands();

这种方法我的理解是将进程中等待的Binder命令,一并提交给CPU处理,当然这会造成一些堵塞,我们知道线程的运行也是依靠CPU来运转,所以我觉得这种方法才是AsyncTask能够同步运行的关键.

在每一个任务运行完时,都会把当前进程的等待任务提交,然后堵塞,等都完毕,该进程内没有Thread提交时,一起返回,从而形成同步.

而在这里之所以不去使用队列线程池的原因也在这,由于队列线程池实现了锁的机制,而且通过它的代码我们能够得知它是处理完一个任务,才会去下个任务,这一块也是AsyncTask默认能够实现串行的原因.


postResult()方法

    private Result postResult(Result result) {
@SuppressWarnings("unchecked")
//正常的把结果通过InternalHandler发送给UI线程.
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}

这种方法,没有太多可介绍的.熟悉Handler机制的,自然就能看懂.当postResult()方法运行后,InternalHandler

会调用AsyncTask的finish()方法.


finish()方法

    private void finish(Result result) {
//推断任务是否被取消,假设被取消则回调onCalled()
if (isCancelled()) {
onCancelled(result);
} else {
//请求成功
onPostExecute(result);
}
//变更AsyncTask的状态
mStatus = Status.FINISHED;
}

finish()是返回结果的方法,无论AsyncTask是否被取消都会将该AsyncTask的状态变更成FINISHED.


总结

最后基本上整个AsyncTask的介绍也就清晰了,本文的介绍的思路是:

1.先介绍AsyncTask的核心构成.
2.在介绍AsyncTask的工作流程:
execute() -->executeOnExecutor() -->postResult() --> finish()
3.还了解了AsyncTask中的串行和并行的特点,以及AsyncTask实现并行和串行的原理.

AsyncTask内部工作流程图解

这图可能画的有点复杂,事实上细致对比上文的逻辑和源代码看,你一定会弄明确的.而且通读全然文之后,你会发现AsyncTask并不难,原来是这么简单.


參考文章

1.安卓艺术探索

带你轻松看源代码---AsyncTask(异步任务)的更多相关文章

  1. Android中使用Thread线程与AsyncTask异步任务的区别

    最近和几个朋友交流Android开发中的网络下载问题时,谈到了用Thread开启下载线程时会产生的Bug,其实直接用子线程开启下载任务的确是很Low的做法,那么原因究竟如何,而比较高大上的做法是怎样? ...

  2. Android线程管理之AsyncTask异步任务

    前言: 前面几篇文章主要学习了线程以及线程池的创建与使用,今天来学习一下AsyncTask异步任务,学习下AsyncTask到底解决了什么问题?然而它有什么弊端?正所谓知己知彼百战百胜嘛! 线程管理相 ...

  3. Android 多线程----AsyncTask异步任务详解

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...

  4. android AsyncTask异步下载并更新进度条

    AsyncTask异步下载并更新进度条    //如果不是很明白请看上篇文章的异步下载 AsyncTask<String, Integer, String> 第一个参数:String 传入 ...

  5. Android异步处理系列文章四篇之二 使用AsyncTask异步更新UI界面

    Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ...

  6. Android异步处理二:使用AsyncTask异步更新UI界面

    在<Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面>中,我们使用Thread+Handler的方式实现了异步更新UI界面,这一篇中,我们介绍一种更为简 ...

  7. 带着问题看redux源码

    前言 作为前端状态管理器,这个比较跨时代的工具库redux有很多实现和思想值得我们思考.在深入源码之前,我们可以相关注下一些常见问题,这样带着问题去看实现,也能更加清晰的了解. 常见问题 大概看了下主 ...

  8. 《奥威Power-BI案例应用:带着漫画看报告》腾讯课程开课啦

    元旦小假期过去了,不管是每天只给自己两次下床机会的你,还是唱K看电影逛街样样都嗨的你,是时候重振旗鼓,重新上路了!毕竟为了不给国家的平均工资水平拖后腿,还是要努力工作的.话说2016年已经过去了,什么 ...

  9. AsyncTask异步交互和httpurlconnection结合使用

    //网络请求数据 package com.baidu.myutils; import java.io.BufferedReader; import java.io.InputStreamReader; ...

随机推荐

  1. [D3] Add image to the node

    We can create node with 'g' container, then append 'image' to the nodes. // Create container for the ...

  2. [React] Create component variations in React with styled-components and "extend"

    In this lesson, we extend the styles of a base button component to create multiple variations of but ...

  3. python把一个列表画柱状图

    https://blog.csdn.net/w113691/article/details/80385534

  4. java——数组

    数组是多个同样数据类型数组组合,当中数据类型是不论什么数据类型. 数组变量是引用类型变量,数组能够作为对象,数组中的每个元素相当于对象的成员变量,所以数组元素能够默认初始化.(博客java--变量分类 ...

  5. linux 内核源代码目录结构

    /arch:目录包括了所有和体系结构相关的核心代码.它下面的每一个子目录都代表一种Linux支持的体系结构,例如i386就是Intel CPU及与之相兼容体系结构的子目录.PC机一般都基于此目录. / ...

  6. 【例题5-6 UVA 540 】Team Queue

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 用两个队列模拟就好. 记录某个队在不在队列里面. 模拟 [错的次数] 在这里输入错的次数 [反思] 在这里输入反思 [代码] #in ...

  7. js实现类似页面广告一段时间自动打开一段时间自动关闭的功能

    js实现类似页面广告一段时间自动打开一段时间自动关闭的功能 一.总结 Window 对象的 open()方法:window.open('测试页面.html','news','height=300,wi ...

  8. 关于fatfs生成的wav文件是空,大小是0的问题

    绝大多数是因为打开错误 调试的时候,编写程序的时候 要记得res=f_open() 要有返回值res的设置

  9. [Angular] Show a loading indicator in Angular using *ngIf/else, the as keyword and the async pipe

    The network may be unreliable and loading data may take time. Thus it is important to give the user ...

  10. Xavier Initialization 的理解与推导(及实现)

    在 caffe mnist tutorial 的实现中,有如下的语句: weight_filter = {type: "xavier"}; 随后的解释中指出,本实现将使用 xavi ...