Android进阶2:线程和线程池(1)—— AsycTask原理解析
Android不允许UI主线程做耗时操作,不允许子线程刷新UI,声明UI控件单线程模式,至于为什么看上篇文章:
这些问题都催生了链接子线程和主线程Handler的诞生,然而Google也为我们提供了一个老牌的Thread + handler的神器:AsyncTask。
AsyncTask用法:
先来看下AsyncTask 的用法:
1. 衍生类的三个泛型限制:Params :doInBackground方法内的参数;Progress:异步过程中,onProgressUpdate更新UI的参数; Result: doInBackground的返回值;
2. onPreExecute:UI线程启动任务之前会回调此方法。
3. doInBackground:再次方法内,可以进行耗时操作,此方法内不能更新UI
4. onProgressUpdate:任务执行过程中,可以在doInBackground方法内调用publishProgress方法获取任务执行的进度,此时会回调此方法; 此方法内能更新UI
5. onPostExecute:当任务执行完毕之后,回调此方法,更新UI;
那么上述几个方法什么时候调用呢?
看源码,老规矩,还是从AsyncTask属性变量入手。
AsyncTask属性变量
对于看源码之前成员变量的分析,还是很有必要的,至少看完之后,我们能知道,该源码的时候都用到了哪些知识点。
//THREAD_POOL_EXECUTOR线程池的一些设置:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
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());
}
};
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);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
//handler发送消息的what标记
private static final int MESSAGE_POST_RESULT = 0x1; //执行结果
private static final int MESSAGE_POST_PROGRESS = 0x2; //执行过程
//单任务线程池,保证一次一个任务进行
public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); //一次一个的执行任务(先进先出,保证顺序执行)
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static InternalHandler sHandler; //创建的handler(如果Looper是null,就赋值给mHandler)
private final WorkerRunnable<Params, Result> mWorker; //创建任务
private final FutureTask<Result> mFuture; //监控任务执行的进度
private volatile Status mStatus = Status.PENDING; // 默认任务标记是:准备状态
private final AtomicBoolean mCancelled = new AtomicBoolean(); //任务是否取消
private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); //任务调度(futureTask方法是否发送消息)
private final Handler mHandler; //全局发送消息并处理消息的handler
//三种枚举类型
public enum Status {
/**
* Indicates that the task has not been executed yet.
*/
PENDING, //准备执行
/**
* Indicates that the task is running.
*/
RUNNING, //正在执行
/**
* Indicates that {@link AsyncTask#onPostExecute} has finished.
*/
FINISHED, //执行完毕
}
从上述成员变量,得出的信息是:
- 使用了线程池,还是两个线程池。并且这两个线程池是整个应用下有且只有这一份,无论创建多少个AsyncTask对象
- 嗅到了handler的气息
- 声明了异步任务
成员变量看完了,是不是该构造方法了?
通过成员变量需要思考的问题:
- 线程池,还两个,这是干什么用的?
- 枚举类型是干什么用的?
- 定义的两个handler标记,发送消息?发送的都是什么消息?
AsyncTask的构造方法
般使用的时候都是直接调用的空的构造方法,源码示下:
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
//翻译:创建一个新的异步任务,必须在UI现呈上调用此构造函数
public AsyncTask() {
this((Looper) null); //空的looper对象
}
从上述源码可以看出,内部调用了另一个构造方法,而且参数是Looper,只不过此时的looper的null。但是是不是已经嗅到了一股handler的气息。 另外需要注意:创建一个新的异步任务,必须在UI现呈上调用此构造函数,这是官方说的!!!
public AsyncTask(@Nullable Looper callbackLooper) {
// looper是否是null 或者 是否是Ui主线程的Looper ?
//创建UI线程下的handler对象
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); //设置线程优先级
//noinspection unchecked
result = doInBackground(mParams); //设置异步结果
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 {
//FutureTask的get()方法是阻塞的,在结果计算完成之后,调用此方法,才会有数据
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);
}
}
};
}
从源码可以看书,总共做了两件事:
- 创建处理消息的handler
- 创建worker和future(这个是创建线程的三种方式之一)
简单提一下worker和future的理解,workerRunnable是对任务做了封装,做干活的;而futureTask是对任务的执行状态做了封装,就是任务的执行状态。
先看下mWorker的call方法,既然call是干活的,那么就是运行在子线程了。内部首先将mTaskInvoked置为的true,而mTaskInvoked的类型是AtomicBoolean,多个线程中,也就是告诉别人我以在处理了,你不能处理了。 然后看到了设置doInBackground的回调,是不是明了了,首先我们知道了doInBackground是在异步线程中调用的,所以我们复写的此方法才能进行耗时操作,但是不是更新UI。当doInBackground执行完毕之后,
最后看到了一个finally,里面是postResult方法。
private Result postResult(Result result) {
//通过构造方法中创建的handler创建消息
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result));
message.sendToTarget(); //发送消息
return result;
}
通过上述源码,了解的信息是:通过我们创建的mHandler发送了一条消息,至于消息是什么?标记是:MESSAGE_POST_RESULT,在哪里接收? 往下看:handleMessage方法
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@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: //异步过程中回调给UI层更新view
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
看到了MESSAGE_POST_RESULT标记内的代码是: finish方法,继续跟进:
private void finish(Result result) {
if (isCancelled()) { //取消任务
onCancelled(result); //取消任务
} else {
onPostExecute(result); //熟悉的配方,熟悉的味道
}
mStatus = Status.FINISHED; //将标记更改。
}
如果流程没取消,就进入到了onPostExecute方法,等等,onPostExecute方法? 这不是就是我们获取结果,更新Ui的方法嘛。还有之前抛出的问题三:MESSAGE_POST_RESULT标记就是为了handler的处理区别消息类型,作用是发送执行结果给UI层
通过AsyncTask的构建,我们知道了,当任务执行起来之后,在子线程先执行doInBackfruond方法,最后如论结果如何,都通过之前创建的handler,发送消息,切换到主线程执行onPostExecute方法。
构造方法好了,是不是该执行任务了?
启动AsyncTask
在创建完AsyncTask之后,需要调用 execute方法开始执行任务,看下execute的源码:
/**
* This method must be invoked on the UI thread.
* 翻译:此方法必须运行在UI主线程上。
*/
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
/**
* @param exec 线程池
* @param params 输入的参数
* @return
*/
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) { //解释了一个AsyncTask实例只能执行一次的原因
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); //开始运行(Future加入到线程池中)
return this;
}
从上述代码可以看出:
- 此方法必须运行在主线程中(MainThread 和 方法描述都能看出)
- 此方法内使用了线程池,该线程池是常量SERIAL_EXECUTOR线程池,实际上执行的是线程池execute方法。
- 线程池执行方法,参数是AsyncTask构造方法中的FutureTask对象,那么是不是这个线程池执行了FutureTask任务呢?
- 方法内做了判断,如果此任务已经在执行或者执行完毕了,但是又一次执行,会抛出异常。所以结论是:如果需要执行多个任务就需要创建相对应数量的AsyncTask对象来执行,不能重复执行任务。 例如:一个AsyncTask对象,只能执行一次,多次执行报错。
- onPreExecute方法,看到了吗? 也就是说onPreExecute回调方法,会在execute执行任务之前就调用的。
通过上述结论,那么我们的之前抛出的问题2:枚举类型是干什么用的?就有了答案了。
继续分析,既然实际上执行的是线程池的execute方法,那么就有必要先看下 SERIAL_EXECUTOR的创建:
//单任务线程池
private static class SerialExecutor implements Executor {
//双向链表(实际上存储的是FutureTask对象)
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) { //是否有任务正在执行 (第一次进来mActive肯定是null)
scheduleNext();
}
}
protected synchronized void scheduleNext() {
//由于THREAD_POOL_EXECUTOR是可以并发执行的线程池(最多同时运行5个),
//所以将if改为while,即可并发执行任务
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
SerialExecutor有两个变量:ArrayDeque 和 Runnable;
ArrayDeque :循环队列,就是可以无休止的存储数据,泛型是Runnable接口对象。Runnable对象表示正在执行的任务对象,不是FutureTask对象。为什么不是FutureTask对象呢?往下看分析。
execute方法内部代码逻辑:在循环队列尾部把新建的Runnable对象添加了进去。接下来是判断Runnable的mActive对象是否是null,如果是null,表明第一次进入此方法,直接执行sheduleNext();方法。
至于sheduleNext方法内部逻辑还是很简单的,首先队列出队赋值给mActive,判断是否是null,如果不是null,表明有任务可以执行,此时就调用THREAD_POOL_EXECUTOR.execute(mActive);
,这? THREAD_POOL_EXECUTOR? 这是个异步线程池,原来SERIAL_EXECUTOR线程池并不执行任务,而是把任务添加到队列中,执行任务的事情交给了THREAD_POOL_EXECUTOR异步线程池。那么问题来了:
THREAD_POOL_EXECUTOR是异步线程池,能够同时执行几个线程,这里最多能并发执行多个线程吗?
再看sheduleNext()方法:
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
可以看到这是个if,这是顺序执行的,就是execute一次只能执行一个任务,答案就是:一次只能执行一个任务,不能并发执行。但是if之后就没有了啊,队列未执行的任务就得不到执行了啊?这不行啊!别急,现在再看下任务入队的代码:
mTasks.offer(new Runnable() { //入队操作(末尾插入)
public void run() {
try {
r.run(); //执行任务
} finally { //执行完上一个任务之后,一定会执行下一个如果有的话
scheduleNext();
}
}
});
可以看到 r.run()任务执行之后,看到finally 了吗???无论如何都会再一次调用scheduleNext()方法,也就上一个任务执行完了,紧接着调用scheduleNext()方法,这样队列中的任务都会得到执行。
等等,这个r.run方法执行了,但是mWork内部的call方法什么时候执行呢? 看下FutureTask的源码:
public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); //此处回调workRunnable的call方法
......
}
通过上面的源码分析,初步了解了AsyncTask的运行流程,知道了onPreExecute, doInBackground, onPostExecute方法调用的时机,不对,还少一个onProgressUpdate(),= - = .先看下onProgressUpdate的源码:
/**
* Runs on the UI thread after {@link #publishProgress} is invoked.
* The specified values are the values passed to {@link #publishProgress}.
*
* 翻译:通过publishProgress调度之后,此方法运行在UI主线程,参数值通过publishProgress传递
* @param values The values indicating progress.
*
* @see #publishProgress
* @see #doInBackground
*/
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onProgressUpdate(Progress... values) {
}
通过上述方法的描述翻译,可以确定两点:
- onProgressUpdate方法是运行在UI主线程中的
- 该方法的回调是通过publishProgress调度出发的。
所以我们有必要看下publishProgress的源码:
@WorkerThread
protected final void publishProgress(Progress... values) { //在doInBackground中调用
if (!isCancelled()) {
//给主线程发送消息
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
首先方法注释WorkerThread限定了 此方法运行在子线程中的,子线程?是不是串联起来了呢? publishProgress可以在doInBackground方法中手动调用,然后通过handler发送消息(MESSAGE_POST_PROGRESS),再看下handleMessage方法:
@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: //异步过程中回调给UI层更新view
result.mTask.onProgressUpdate(result.mData);
break;
}
}
MESSAGE_POST_PROGRESS内部调用了onProgressUpdate方法,闭环了吧。
doInBackground方法内 —> publishProgress方法,发送消息 —–> onProgressUpdate方法 —>开发者自己处理逻辑
至此,AsyncTask的基本原理就分析完了,再来总结一下开头抛出的三个问题:
THREAD_POOL_EXECUTOR:负责执行任务;
SERIAL_EXECUTOR:将任务都添加到双向队列中,然后一个一个取出来,保证单任务执行
1. 线程池,还两个,这是干什么用的?
THREAD_POOL_EXECUTOR:负责执行任务;SERIAL_EXECUTOR:将任务都添加到双向队列中,然后一个一个取出来,保证单任务执行
2. 枚举类型是干什么用的?
枚举是用来标记AsyncTask对象的状态的。一个AsyncTask对象只能执行一次
3. 定义的两个handler标记,发送消息?发送的都是什么消息?
MESSAGE_POST_RESULT:回调任务执行完毕的结果;MESSAGE_POST_PROGRESS回调执行过程中的进度
本位内容感谢《Android开发艺术探索》
本文的内容希望能帮助到你,文中如果有错,欢迎之处,以便纠正,谢谢~
android开发交流群:543671130
下一篇: RXJAVA