默认情况下,在一个相同的android应用程序当中,其里面的组件都是运行在同一个线程里面的,这个线程我们称之为main thread,当我们通过某个组件来启动另一个组件的时候,这个时候默认是在同一个线程当中完成的。Main thread用来加载我们的ui界面,完成系统和我们用户之间的交互,并将交互的结果展示给我们用户,所以main thread 又称为ui thread。因此,在android的多线程编程中,有两个重要的原则:1.绝对不能在ui thread当中进行耗时操作,不能阻塞我们的ui thread,2.不能在ui thread之外的线程当中操纵我们的ui元素。但是,如果我们想要进行一些耗时操作,而操作途中产生的结果需要随时反馈给我们的ui界面或者更通俗说是主线程,同时还不能阻塞,那么该如何实现呢?Android中提供了异步处理函数AsyncTask来实现上述需求。
在使用AsyncTask之前,记得AsyncTask的相应限制:
1. AsyncTask的类必须在主线程中加载
2. AsyncTask的对象必须在主线程中创建
3. execute方法必须在Ui线程调用
4. 不要在程序中调用onPreexecute,onPostExecute,doInbackground,onProgressUpdate方法
5. 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行异常
6. 在android1.6之前,AsyncTask是执行串行任务的,android1.6的时候开始采用线程池的方式来处理并行任务,但是从android3.0开始,为了避免带来的并发错误实现线程安全,AsyncTask又采用一个线程来串行执行任务,但我们仍然可以使用executeOnExecutor来并行的执行任务
其中1.2.3.5点的理由在下述中会解释清楚。
接下来介绍AsyncTask的相关函数
1. onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2. doInBackground(Params...)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过 return 语句来将任务的执行结果返回,如果 AsyncTask的第三个泛型参数指定的是 Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行 UI操作的,如果需要更新 UI元素,比如说反馈当前任务的执行进度,可以调用 publishProgress(Progress...)方法来完成。
3. onProgressUpdate(Progress...)
当在后台任务中调用了 publishProgress(Progress...)方法后,这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。
4. onPostExecute(Result)
当后台任务执行完毕并通过 return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
一个完整的asyncTask的例子:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show(); // 显示进度对话框
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload(); // 这是一个虚构的方法
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
// 在这里更新下载进度
progressDialog.setMessage("Downloaded " + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss(); // 关闭进度对话框
// 在这里提示下载结果
if (result) {
Toast.makeText(context, "Download succeeded",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, " Download failed",
Toast.LENGTH_SHORT).show();
}
}
}
如果想要启动这个任务,只需编写以下代码即可:
new DownloadTask().execute();
下面结合源代码来分析AsyncTask的工作原理,首先来看其调用方法execute(),直接上源代码:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
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;
}
在这里有很多疑问,mWorker.mParams = params是什么鬼?为什么execute进来的参数只有mFuture,mFuture又是什么?难道不需要传入params了吗?首先来看mWorker.:
private final WorkerRunnable<Params, Result> mWorker;
可以看到mWorker关注的就是输入的参数Params和输出的参数Result,这里再来看WorkerRunnable的定义:
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
看了代码之后原来是这样:输入的params先放到mWorker里存起来了,那么mFutrue呢?直接来看源代码:
private final FutureTask<Result> mFuture;
可以看到mFuture就只关注输出的参数result了,那么params哪里去了?怎么这里就直接变成esult了,所以我猜想,肯定是mFuture中对我们存入params的mWorker.mParams做了处理,为了验证我们的猜想,我们再来看mFuture是在哪里进行初始化的,最后发现是在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);
}
}
};
}
可以看到mFuture其实是封装之后的mWorker,而mWorker的参数不就是关键的Params, Result吗?因此可以理解为mWorker关注的是输入的参数和输出参数,而其call方法最终会在线程池THREAD_POOL_EXECUTOR中执行,这里也可以看成是任务被处理的地方,而mFuture只有Result这个参数,表示到它这里任务已经处理完了,它只关心处理之后的结果,即Result,那么,我们来看看任务被处理的地方call方法,直接上源代码:
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);
}
mTaskInvoked.set(true)表示当前方法已经被调用过了,同时在执行 doInBackground之前将进程的环境切换为BACKGROUND环境:
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
紧接着就正式进行doInBackground工作了,获得了处理的结果result ,将其传给postResult来实现对返回结果的最终处理,因此来看其源代码:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
在上面代码中,postResult会通过变量sHandler(通过getHandler()获得)来发送一个MESSAGE_POST_RESULT的消息(向自己发消息,即发送result),那么这个result在哪处理呢?来看sHandler的初始化:(来波广告,有对handler或者android消息机制不清楚,不理解的同学可以参考下我的前几篇博客哈)
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;
}
}
}
注意看在源代码中InternalHandler是静态变量,那么在java中的静态变量代表了什么呢?意味着声明为static的变量实质上就是全局变量。当声明一个对象时,并不产生static变量的拷贝,而是该类所有的实例变量共用同一个static变量。静态变量与静态方法类似。所有此类实例共享此静态变量,也就是说在类装载时,只分配一块存储空间,所有此类的对象都可以操控此块存储空间,因此为了对成功的实现对ui进行操作,sHandler这个对象的创建必须在主线程中。
又由于静态成员会在加载类的时候进行初始化,因此变相意味着AsyncTask类必须在主线程中进行加载。接下来再来看源码中出现的的finish方法:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
源码很简单,不想讲了,同时在源码中也可以看到对于执行进度不同的任务,在asyncTask中是如何进行标记的呢?由 mStatus = Status.FINISHED;可知是根据枚举Status进行标记的,Status中有三种状态:
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,
}
到这里,我们再来回到execute说调用的executeOnExecutor来解答我们一开始的疑问:
mWorker.mParams = params;
exec.execute(mFuture);
其实在executeOnExecutor中上述两句代码就已经实现了异步消息的处理了,因此在execute中进来的就只有mFuture了。
AsyncTask的异步处理原理基本也快分析完了,那么我们就继续往下走在调用executeOnExecutor的时候我们其实传入的是sDefaultExecutor,那么这个是啥呢?不废话,上代码:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
在上述代码中sDefaultExecutor实际上是一个串行的线程池,一个进程中所有的asyncTask全部在这个串行的线程池中排队执行:(线程池的相关分析在下篇博客,敬请期待)
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 的实现可以看到asyncTask的排队执行过程。首先系统会把asyncTask的params参数对应源代码中的mWorker封装为FutureTask对象,对应源代码中的mFuture,在这里FutureTask是一个并发类,充当了Runnable的作用。接着这个FutureTask会交给SerialExecutor的execute方法去处理,代码中对应为exec.execute(mFuture);该方法实现了将FutureTask插入到任务队列 mTasks中:
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
调用r.run方法(可以理解为执行了mWorker的call方法)实现对任务的处理,具体的处理过程上文已经分析过了(请参见上文对call的分析),这里就不啰嗦了。接着回到任务队列,如果这个时候没有正在活动的SerialExecutor任务,就会调用SerialExecutor的scheduleNext方法来执行下一个asyncTask任务,同时当一个asyncTask任务执行完后,asyncTask会继续执行其他任务直到所有任务都被执行完,从这一点可以看出,asyncTask是串行执行的。
AsyncTask里有两个线程池(SerialExecutor 和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中SerialExecutor和InternalHandler都已经介绍过了,还剩下THREAD_POOL_EXECUTOR。
我们来看看在AsyncTask中的SerialExecutor 和THREAD_POOL_EXECUTOR是如何相互配合工作的。我们再结合SerialExecutor的源代码来看,mTasks.offer()方法可以理解为将params参数封装为futureTask对象,然后再将其通过offer()来插入消息队列mTasks当中。因此SerialExecutor 中是先简单的将任务进行排队(当然,这里排队的任务实际上是充当了runnable作用的futureTaks对象mFuture),然后再对队列中的当前任务进行处理,当这个任务的所有内容都被处理完了,再来调用scheduleNext()方法:
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
再来看THREAD_POOL_EXECUTOR的定义:
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
其实就是最常规的线程池的定义方式(有关线程池的只是以后会讲)。再来看scheduleNext() 中的关键参数mActive,实际定义是runnable,而mTasks.poll()方法表示获得任务队列中最新的任务,也就是我们一开始传进去的mFuture。
至此,AsyncTask的所有流程基本都分析完了。