博主声明:
转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。
在 Android 异步处理中,有一个 AnsycTask 非常的实用,相信大部分的 Android 开发者都有使用过。我们 Java 中的同步和异步这个概念,也许有人还不是非常清楚,我们在学习线程那一部分内容的时候,首先引出的两个概念就是同步和异步,后面才学习线程。
同步和异步介绍:
那么,先看看什么叫做同步,我个人的理解就是:比如,一个问题解决的步骤需要1、2、3这三个步骤才能完成,同步就是指我们必须先完成1才能继续完成2,最后完成3。这种情况就好比我们数学中的大题,大题中有三个小题,我们必须完成第1题,然后用第1题的答案参与第2题计算,最后要综合1、2完成第3题,才能得出正确答案,是一环扣一环的,必须等待前一个步骤的完成才能继续进行。
然后,异步就是指:这个问题虽然有1,2,3才能完成,但是它们之间的联系不太密切,也就是说可以任意的顺序完成这三个步骤,不需要等待谁。举个例子,比如有一组去参加数学竞赛,这个小组有三名学生,他们需要完成90道选择题,计时开始,他们就分配每人30题同时开始做,等到做完时,就把答案合并到一起,才能完成比赛。从这个例子可以看出,异步可以同时操作一个或多个任务。
复习一下同步和异步的概念,是为了让我们更好的去理解线程。在 Android 中只有存在两种线程,一个是主线程,也称 UI线程,而其他的都是子线程。而子线程可以任意的创建,每个子线程都可以同时进行一些操作,完成自己的任务,比如上面的三个同学就像三个子线程一样,而子线程做到都是异步任务。
AnsycTask 为什么是异步的呢,下面我们从源码一探究竟吧。
首先,来看看我画的一张思维导图:
那么,为什么说 AnsycTask 是异步任务呢,我们在 Android 中创建它的时候,哪里有看到与线程相关的代码了?例如,我们一般创建一个 Task 去继承 AnsycTask,代码如下:
package com.example.xww.myapplication;
import android.os.AsyncTask;
/**
* @author xww
* @blog https://blog.csdn.net/smile_running
* @name 威威猫
*/
public class MyTask extends AsyncTask<Void, Integer, Void> {
@Override
protected void onPreExecute() {
super.onPreExecute();
//异步任务开始
}
@Override
protected Void doInBackground(Void... voids) {
//执行在子线程中
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
//任务进度
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
//任务完成
}
}
1、通常要求我们必须重写 doInBackground() 方法,这是为什么呢?
从源码可以看出,因为 AnsycTask 是一个抽象类,而 doInBackground() 是一个抽象方法,我们继承抽象类时,必须重写抽象方法。
抽象类:
抽象方法:
/**
* Override this method to perform a computation on a background thread. The
* specified parameters are the parameters passed to {@link #execute}
* by the caller of this task.
*
* This method can call {@link #publishProgress} to publish updates
* on the UI thread.
*
* @param params The parameters of the task.
*
* @return A result, defined by the subclass of this task.
*
* @see #onPreExecute()
* @see #onPostExecute
* @see #publishProgress
*/
@WorkerThread
protected abstract Result doInBackground(Params... params);
从源码上面的注释,我们大致可以翻译出来,它说重写这个方法去执行一些计算是在后台线程中进行的,也就是在子线程中去执行。还说了通过 publishProgress() 方法可以更新 UI 操作,我们来一探究竟。
2、为什么 doInBackground() 方法在子线程中执行?
首先,当然是它的调用方式,我们开启异步任务时,要调用 execute() 方法,我们跟着方法进行找源代码。
MyTask myTask = new MyTask();
myTask.execute();
开始,会去调用构造函数,初始化一些参数的操作,代码如下:
AsyncTask 构造函数:提供三个构造函数
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
this((Looper) null);
}
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*
* @hide
*/
public AsyncTask(@Nullable Handler handler) {
this(handler != null ? handler.getLooper() : null);
}
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*
* @hide
*/
public AsyncTask(@Nullable Looper callbackLooper) {
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 {
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);
}
}
};
}
这里有一些差距,第一个是无参的,用的是 AsyncTask 类中所有默认的。而第二个构造函数,要我们传入一个 Handler 对象,使用的是我的 Handler 对象的 Looper,而第三个需要传入 Looper 对象,但是不管调用哪个,最终都会去执行第三个构造函数,这里我不太明白,直到自己去尝试了一下,写了一个例子就恍然大悟,原来可以这样写。以前的话,一般都是在内部自己 new 自己,这下学到了:
package com.example.xww.myapplication;
/**
* @author xww
*/
public abstract class Test {
public Test() {
this(null);
}
public Test(int num) {
this(String.valueOf(num));
}
public Test(String str) {
}
}
那么,我们看第三个构造函数的方法具体初始化了哪些东西。首先是一个 worker 的初始化操作:
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;
}
};
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
WorkerRunnable 是 AsyncTask 一个内部的抽象类,内部做了一个参数的赋值。它实现了 Callable 接口,重写了 call() 方法,这个方法也是工作在子线程中的,不过它与 Runnable 接口的区别就是,run() 方法返回的是 void 类型,而 call() 是一个泛型,也就是我们可以在子线程中返回任务的执行结果。
这里的 Result 就是我们传入的泛型,它的结果是在 doInBackground() 执行后返回的。这里也可以很好的说明,我们的 doInBackground() 方法在子线程中执行的原因。
还有一个初始化就是对我们的 FutureTask 的初始化,FutureTask 是一个针对 Callable 接口进行状态监听的实现类,它可以判断任务是否取消、是否完成,并且可以获得任务执行完的返回值等一些操作的封装类。这里的话代码这样的:
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);
}
}
};
FutureTask 类是对上面的一个 WorkerRunnable 类的对象进行监听的,而 WorkerRunnable 实现了 Callable 接口,而它的返回值是交于 doInBackground() 方法去执行,所以它要等待的就是 doInBackground() 方法的返回结果,在 done() 方法中去监听任务的完成情况,这里是监听 postResult() 方法没有被执行时。
实例化完对象后,需要去调 execute() 方法后才能执行工作。这里来看看 execute() 方法是如何执行的:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
myTask.execute() 之后,它会去调用 executeOnEcecutor() 这个方法:
/**
* <p>This method must be invoked on the UI thread.
*
* @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a
* convenient process-wide thread pool for tasks that are loosely coupled.
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
* @see #execute(Object[])
*/
@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;
}
这里有一个状态枚举类,用于标记这个 Task 的生存时间,分别是 Task 开始,Task 进行中,Task 完成。看源码,默认的 Status 是 PENDING 状态。
/**
* Indicates the current status of the task. Each status will be set only once
* during the lifetime of a task.
*/
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,
}
我们为什么不能重复调用 myTask.execute() 方法的原因是:通过下面这段代码就知道了,它这能被调用一次,再次调用就会抛出异常了。
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)");
}
}
首先,我们调用 myTask.execute() 方法的时候,把 Task 状态改为 RUNNING ,并最先得到执行的就是 onPreEcecute() 方法。也就是说,我们这里可以让子类重写 onPreEcecute() 做一些初始化操作。
然后调 exec.execute(mFuture),这里的 execute() 方法是一个接口中的抽象方法。我们就要看它在哪里去实现的,找到它实现的位置代码:
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 类去实现 Executor 接口,这里的 execute 就是接口中的抽象方法,我们看它用了 synchronized 修饰了,所以意味着 AsyncTask 它是线程安全的。
这里用了一个队列 ArrayDeque<Runnable> 来存储 Runnable 对象,THREAD_POOL_EXECUTOR 是一个线程池,所以,可以看出,AsnycTask 内部还有一个线程池来维护,线程池初始化代码:
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
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;
}
当从 ArrayDeque<Runnable> 队列中取出的 Runnable 对象不为空的时候,线程池就会调用 execute() 方法,把 Runnable 对象传进去,否则会 new 一个 Runnable 对象,不管如何,最终它们都是跑到 Runnable 接口中的 run 方法里面执行,而你肯定会奇怪,为什么 THREAD_POOL_EXECUTOR 也可以调用 execute() 方法?
原来,这里的线程池 TheadPoolExecutor 类也间接的实现了 Executor 接口,它首先实继承 AbstractExecutorService 类,而 AbstractExecutorService 类又实现了 ExecutorService 接口,而 ExecutorService 接口又继承 Executor 接口,绕了一大圈才明白,这里的 TheadPoolExecutor 线程池类与 SerialExecutor 类一样,都是实现了 Executor 接口,所以这里的 THREAD_POOL_EXECUTOR 调用 execute() 方法才会跑到线程池中执行的一个重要原因。并且这里的线程池是一个单利模式,使用了静态代码块。
经过这样的一番追根究底,我们终于弄明白 doInBackground() 方法在子线程中执行的原因。不过还没有结束,从源码可以看出这个SerialExecutor 类的作用是把 Runnable 加入到线程池当中去执行,并设置了默认的线程池。
/**
* 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 volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
AsyncTask 内部维护的一个默认的线程池参数如下:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
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);
线程池的核心线程大小:2 ~ 4 个之间,不会小于2也不会大4。最大线程数是 CPU 的两倍 +1 个,线程结束任务后,可以继续存活 30s,还有使用了 AtomicaInteger 这个类实现了 new Thead 数量自增的创建,AtomicaInteger 类是线程安全的,这里不看它的源码了。最后是一个阻塞队列,也就是线程池中可以添加的最大 Runnable 的数量,也就是最大的任务数量128个。
当任务执行结束了之后,我们之前说过的一个 FutureTask 就会监听到任务完成了,在 done() 方法中就会调用 postResult() 返回结果。
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<Result>(this, result));
message.sendToTarget();
return result;
}
postResult() 为什么是在 UI 线程中执行的,从上面的代码中,可以很明显的看出来。它调用了 Handler 的 obtainMessage() 方法发送消息,并把结果传递过去。它的结果是一个泛型集合,代码如下:
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
然后在 Handler 中,通过消息分类进行接收结果:
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:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
我们看到,在消息循环里面,处理消息分为两个状态,一种是任务完成后,返回执行的结果,另一种是在任务执行当中的状态,用于进度更新操作。我们看看 finish() 方法代码:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
首先得判断任务是否被取消,否则就调用 onPostExecute() 方法把结果返回,然后任务状态就会标记为完成。我们在子类中,一般都是重写 onPostExecute() 方法获取执行结果的。在 onProgressUpdate() 方法中,可以获取任务执行的进度,比方说开启进度条来显示进度,就重写这个方法,并且这两个方法都是在 UI 线程中执行的。
还有最后一个,为什么 publishProgress() 方法也是在 UI 线程的,通过上面的介绍,我们大概就能猜到了,看看它的代码吧,也是差不多:
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
到此为止,我们把 AsyncTask 的工作原理以及源码全部分析了一遍,整个 AsyncTask 工作的流程就是这么一步一步下来的,通常在刚刚开始学习 AsyncTask 的时候,我们只会知道它怎么去用,只知道 onInBackground() 方法就是在子线程中执行的,而它其他都是在主线程中执行的,这是远远不够的,那些都是止步于 API 的调用层面,还是要深入源码的理解。