线程在 Android 中是一个很重要的概念,从用途上来说,线程分为主线程和子线程,主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时操作。由于 Android 的特性,如果在主线程中执行耗时操作那么就会导致无法及时地响应,甚至 ANR,因此耗时操作必须放在子线程中去执行。
除了 Thread 外,Android 中可以扮演线程角色的还有很多,比如 AsyncTask 和 IntentService,同时 HandlerThread 也是一种特殊的线程。AsyncTask、IntentService 以及 HandlerThread 的表现形式都有别于传统的线程,但是他们的本质仍然是传统的线程。对于 AsyncTask 来说,它的底层用到了线程池,对于 IntentService 和 HandlerThread 来说,它们的底层则直接使用了线程。不同形式的线程虽然都是线程,但是它们仍然具有不同的特性和使用场景。
AsyncTask 封装了线程池和 Handler,它主要是为了方便开发者在子线程中更新 UI。 HandlerThread 是一种具有消息循环的线程,在它的内部可以使用 Handler。IntentService 是一个服务,系统对其进行了封装使其可以更方便地执行后台任务,IntentService 内部采用 HandlerThread 来执行任务,当任务执行完毕后 IntentService 会自动退出。从任务执行的角度来看,IntentService 的作用很像一个后台线程,但是 IntentService 是一种服务,它不容易被系统杀死从而可以尽量保证任务的执行,而如果是一个后台线程,由于这个时候进程中没有活动的四大组件,那么这个进程的优先级就会非常低,会很容易被系统杀死,这是 IntentService 的优点。
在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制地产生,并且线程的创建和销毁都会有相应的开销。当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并行,除非线程数量小于等于 CPU 的核心数,一般来说这是不可能的。试想一下,如果在一个进程中频繁地创建和销毁线程,这显然不是高效的的做法。正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销。 Android 中的线程池来源于 Java。主要通过 Excutor 来派生特定类型的线程池,不同种类的线程池具有各自的特性。
主线程和子线程
主线程是指进程所拥有的线程,在 Java 中默认情况下一个进程只有一个线程,这个线程就是主线程。主线程主要处理界面交互相关的逻辑,因为用户随时会和界面发生交互,因此主线程在任何时候都必须有较高的响应速度,否则就会产生一种界面卡顿的感觉。为了保持较高的响应速度,这就要求主线程中不能执行耗时的任务,这个时候子线程就派上用场了。子线程也叫工作线程,除了主线程以外的线程都是子线程。
Android 沿用了 Java 的线程模型,其中的线程也分为主线程和子线程,其中主线程也叫 UI 线程。主线程的作用是运行四大组件以及处理它们和用户的交互,而子线程的作用则是执行耗时任务,比如网络请求、I/O操作等。从 Android 3.0 开始系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出 NetworkOnMainThreadException 这个异常,这样做是为了避免主线程由于被耗时操作所阻塞从而出现 ANR 现象。
Android 中的线程形态
本节将对 Android 中的线程形态做一个全面的介绍,除了传统的 Thread 以外,还包含 AsyncTask、HandlerThread 以及 IntentService,这三者的底层实现也是线程,但是它们具有特殊的表现形式,同时在使用上也各有优缺点。为了简化在子线程中访问 UI 的过程,系统提供了 AsyncTask、AsyncTask 经过几次修改,导致了对于不同的 API 版本AsyncTask 具有不同的表现,尤其是多任务的并发执行上。由于这个原因,很多开发者对 AsyncTask 的使用上存在误区,本节将详细介绍使用 AsyncTask 时的注意事项,并从源码角度来分析 AsyncTask 的执行过程。
AsyncTask
AsyncTask 是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新 UI。从实现上来说, AsyncTask 封装了 Thread 和 Handler,通过 AsyncTask 可以更加方便地执行后台任务以及在主线程中访问 UI,但是 AsyncTask 并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。
AsyncTask 是一个抽象的泛型类,它提供了 Params、Progress 和 Result 这三个泛型参数,其中 Params 表示参数的类型,Progress 表示后台任务的执行进度的类型,而 Result 则表示后台任务的返回结果的类型,如果 AsyncTask 确实不需要传递具体的参数,那么这三个泛型参数可以用 Void 来代替。AsyncTask 这个类的声明如下。
public abstract class AsyncTask<Params, Progress, Result>。
AsyncTask 提供了 4 个核心方法,他们的含义如下表示。
- onPreExecute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作。
- doInBackground(Params...params),在线程池中执行,此方法用于执行异步任务,params 参数表示异步任务的输入参数。在此方法中可以通过 publishProgress 方法来更新任务的进度,publishProgress 方法会调用 onProgressUpdate 方法。另外此方法需要返回计算结果给 onPostExecute 方法。
- onProgressUpdate(Progress...values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。
- onPostExecute(Result result),在主线程中执行,在异步任务执行之后,此方法会被调用,其中 result 参数是后台任务的返回值,即 doInBackground 的返回值。
上面几个方法,onPreExecute 先执行,接着是 doInBackground,最后才是 onPostExecute。除了上述四个方法以外,AsyncTask 还提供了 onCancelled 方法,他同样在主线程中执行,当异步任务被取消时,onCancelled 方法会被调用,这个时候 onPostExecute 则不会被调用。下面提供一个典型的示例,如下表示。
private class DownloadFiledTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground (URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
//Escape early if cancel() is called
if (isCancelled())
break;
}
return totalSize;
}
protected void onProgressUpdate (Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute (Long result) {
showDialog("Downloaded" + result + "bytes");
}
}
在上面的代码中,实现了一个具体的 AsyncTask 类,这个类主要用于模拟文件的下载过程,它的输入参数类型为 URL,后台任务的进程参数为 Integer,而后台任务的返回结果为 Long 类型。注意到 doInBackground 和 onProgressUpdate 方法它们的参数中均包含...的字样,在 Java 中...表示参数的数量不定,它是一种数组形式的参数,...的概念和 C 语言中的...是一致的。当要执行上述下载任务时,可以通过如下方式来完成:
new DownloadFilesTask().execute(url1, url2, url3);
在 DownloadFilesTask 中,doInBackGround 用来执行具体的下载任务并通过 publishProgress 方法来更新下载的进度,同时还要判断下载任务是否被外界取消了。当下载任务完成后,doInBackground 会返回结果,即下载的总字节数。需要注意的是,doInBackground 是在线程池中执行的。onProgressUpdate 用于更新界面中的下载进度,它运行在主线程,当 publishProgress 被调用时,此方法就会别调用,当下载任务执行完成后,onPostExecute 方法就会被调用,它也是运行在主线程中,这个时候我们就可以在界面上做出一些提示,比如弹出一个对话框告知用户下载已经完成。
AsyncTask 在具体使用过程中也是有一些条件限制的,主要有如下几点:
- AsyncTask 的类必须在主线程中加载,这就意味着第一次访问 AsyncTask 必须发生在主线程,当然这个过程在 Android 4.1 及以上版本中已经被系统自动完成。在 Android 5.0 的源码中,可以查看 ActivityThread 的 main 方法,它会调用 AsyncTask 的init 方法,这就满足了 AsyncTask 的类必须在主线程中进行加载这个条件了。
- AsyncTask 的对象必须在主线程中创建
- execute 方法必须在 UI 线程调用
- 不要在程序中直接调用 onPreExecute、onPostExecute、doInBackground 和 onProgressUpdate 方法。
- 一个 AsyncTask 对象只能执行一次,即只能调用一次 execute 方法,否则会报运行时异常。
- 在 Android 1.6 以前,AsyncTask 是串行执行任务的,Android 1.6 的时候 AsyncTask 开始采用线程池处理并行任务,但是从 Android 3.0 开始,为了避免 AsyncTask 所带来的并发错误,AsyncTask 又采用一个线程来串行执行任务。尽管如此,在 Android 3.0 以及后续的版本中,我们仍然可以通过 AsyncTask 的 executeOnExecutor 方法来并行地执行任务。
AsyncTask 的工作原理
为了分析 AsyncTask 的工作原理,我们从它的 execute 方法开始分析,execute 方法又会调用 executeOnExecutor 方法,它们的实现如下所示。
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.");
break;
case FINISHED:
throw new IllegelStateException("Cannot execute task: the task has already been executed (a task can be executed only once)");
break;
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
在上面的代码中,sDefaultExecutor 实际上是一个串行的线程池,一个进程中所有的 AsyncTask 全部在这个串行的线程池中排队执行,这个排队执行的过程后面会在进行分析。在 executeOnExecutor 方法中,AsyncTask 的 onPreExecute 方法最先执行,然后线程池开始执行。下面分析线程池的执行过程,如下所示。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
public synchronized void execute (final Runnable r) {
mTasks.offer (new Runnable() {
public void run () {
try {
r.run();
} finall {
scheduleNext();
}
}
});
if (mActive == null){
scheduleNext ();
}
}
protected synchronized void scheduleNext () {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
从 SerialExecutor 的实现可以分析 AsyncTask 的排队执行的过程。首先系统会把 AsyncTask 的 Param 参数封装为 FutureTask 对象,FutureTask 是一个并发类,在这里它充当了 Runnable 的作用。接着这个 FutureTask 对象插入到任务队列 mTask 中,如果这个时候没有正在活动的 AsyncTask 任务,那么就会调用 SerialExecutor 的 scheduleNext 方法来执行下一个 AsyncTask 任务。同时当一个 AsyncTask 任务执行完成后,AsyncTask 会继续执行其他任务直到所有的任务都被执行为止,从这一点可以看出,在默认情况下,AsyncTask 是串行执行的。
AsyncTask 中有两个线程池(SerialExecutor 和 THREAD_POOL_EXECUTOR) 和一个 Handler (InternalHandler),其中线程池 SerialExecutor 用于任务的排队,而线程池切换到主线程。在 AsyncTask 的构造方法中有如下这么一段代码,由于 FutureTask 的 run 方法会调用 mWorker 的 call 方法,因此 mWorker 的 call 方法最终会在线程池中执行。
mWorker = new WorkerRunnable<Params, Result>() {
public Result call () throw Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResutl(doInBackground(mParams));
}
};
在 mWorker 的 call 方法中,首先将 mTaskInvoked 设为 true,表示当前任务已经被调用过了,然后执行 AsyncTask 的 doInBackground 方法,接着将其返回给 postResult 方法,它的实现如下所示。
private Result postResult (Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
在上面的代码中,postResult 方法会通过 sHandler 发送一个 MESSAGE_POST_RESULT 的消息,这个 sHandler 的定义如下所示。
private static final InternalHandler = new InternalHandler ();
private static class InternalHandler extends Hanlder {
@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;
}
}
}
可以发现,sHandler 是一个静态的 Handler 对象,为了能够将执行环境切换到主线程,这就要求 sHandler 这个对象必须在主线程中创建。由于静态成员在加载类的时候进行初始化,因此这就变相要求 AsyncTask 的类必须在主线程中加载,否则同一个进程中的 AsyncTask 都将无法正常工作。sHandler 收到 MESSAGE_POST_RESULT 这个消息后会调用 AsyncTask 的 finish 方法,如下所示。
private void finish (Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute (result);
}
mStatus = Status.FINISHED;
}
AsyncTask 的 finish 方法的逻辑比较简单,如果 AsyncTask 被取消执行了,那么就调用 onCancelled 方法,否则就会调用 onPostExecute 方法,可以看到 doInBackground 的返回结果会传递给 onPostExecute 方法,到这里 AsyncTask 的整个过程就分析完毕了。
注:此篇摘自 Android 开发艺术。
完~
喜欢有帮助的话: 双击、评论、转发,动一动你的小手让更多的人知道!关注 Android_YangKe
网友评论