一 . 概述
线程分为主线程和子线程,子线程执行耗时操作。
除了 Thread 之外,AsyncTask 和 IntentService 还有 HandlerThread 也是一些特殊的线程。
AsyncTask 封装了线程池和 Handler ,方便在子线程中更新 UI。
HandlerThread 是一种具有消息循环的线程,在其内部可以使用 Handler。
IntentService 是一个服务,其内部是 HandlerThread 来执行任务,完成后自动退出。虽然其作用很像一个后台线程,但是最终其还是一个服务。不容易被系统杀死,从而保证后台任务的执行。
Android 中也引入了线程池的概念,这样可以减少频繁的创建和销毁线程。
Android 中线程也分为主线程和子线程,它们之间的作用区别很明显。
二 . AsyncTask 使用
AsyncTask 的使用方式如下实例 Demo:
class DownloadTask extends AsyncTask<URL, Integer, Boolean> { @Override protected void onPreExecute() { progressDialog.show(); } @Override protected Boolean doInBackground(Void... params) { try { while (true) { int downloadPercent = doDownload(); publishProgress(downloadPercent); // 子线程切换至 UI 线程 if (downloadPercent >= 100) { break; } } } catch (Exception e) { return false; } return true; } @Override protected void onProgressUpdate(Integer... values) { progressDialog.setMessage("当前下载进度:" + values[0] + "%"); } @Override protected void onPostExecute(Boolean result) { // 提示任务执行结果 progressDialog.dismiss(); if (result) { Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show(); } } }
而我们调用时候只需要如下简单语句即可:
new DownloadTask().execute(url1,url2,url3);
对于上述示例,我们注意以下几点:
doInBackgroud 是在线程池中执行的。
onProgressUpdate 用于更新界面中的下载进度,运行在主线程中,当 publishProgress 被调用时,此方法就会被调用。不像 Handler 中需要发送和接受消息来切换线程了。
当下载任务完成后,onPostExecute 方法就会被调用。 它也是运行在主线程中。
三. 源码分析
程序的执行入口是 new AsyncTask().execute() 我们自然要看一下 AsyncTask 的构造方法执行了哪些初始化工作:
public AsyncTask() { // mWorker 对象是一个 Callable 对象 mWorker = new WorkerRunnable<Params, Result>() { // 该方法会在线程池中执行 public Result call() throws Exception { // 表示任务已经执行过了 mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 调用 doInBackgroud 方法并将结果返回至 postResult return postResult(doInBackground(mParams)); } }; // mFuture 对象是一个 FutureTask 对象 mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { //... }; }
容易看出来初始化了两个对象。
接着我们进入 execute() 方法看一看,注意这里传入了 sDefaultExecutor 和 params:
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
进入 executeOnExecutor( ) 方法看一看:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { // task 状态的判断 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(); // 证明 onPreExecute 第一个被执行还在 UI 线程中... mWorker.mParams = params; // params 传入 mWorker 而其与 mFuture 有关 exec.execute(mFuture); // 重点 return this; }
上述代码中我们并没有看到 doInBackground() 方法。而唯一的突破口就是 exec.execute(mFuture) 了。我们可以很清晰的看到 exec 实际上是 sDefaultExecutor ,这是什么?我们自然要去该对象的 execute() 方法中看看了。在上面代码中我们将 mFuture 传入进了 execute() 方法中。FutureTask 是一个并发类,传入进去之后,其充当了 Runnable 对象:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); // 实际上是一个串行的线程池用来排队的private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; private static class SerialExecutor implements Executor { // 定义任务对列,指定类型为 Runnable 对象 final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; // 这里的 execute 就开始在子线程中执行了 public synchronized void execute(final Runnable r) { // 插入到任务队列 mTask 中 mTasks.offer(new Runnable() { public void run() { try { // 突破口 r.run(); } finally { scheduleNext(); } } }); // 如果这时候没有活动的 Asynctask 任务就会执行下一个任务 if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { // 这里可以看出 AT 是串行执行的,队列的 poll() 方法嘛 if ((mActive = mTasks.poll()) != null) { // THREAD_POOL_EXECUTOR 是用来执行任务的 THREAD_POOL_EXECUTOR.execute(mActive); } } }
在上面的 sDefaultExecutor 实际上是一个串行的线程池。一个进程中所有的 AsyncTask 全部在这个串行的线程池中排队执行。
接着我们继续追踪 run() 直到发现这样一句:
result = callable.call();
这句是一开始初始化 AsyncTask 中构造函数中的。原来是一个回调机制啊,再次拿出来分析分析:
// mWorker 对象是一个 Callable 对象 mWorker = new WorkerRunnable<Params, Result>() { // 该方法会在线程池中执行 public Result call() throws Exception { // 表示任务已经执行过了 mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 调用 doInBackgroud 方法并将结果返回至 postResult return postResult(doInBackground(mParams)); } };
接着进入 postResult( ) 方法中看一看:
private Result postResult(Result result) { // 消息携带两个一个常量和一个执行结果 Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); // 发送消息 message.sendToTarget(); return result; }
这里使用 sHandler 对象发出了一条消息,消息中携带了 MESSAGE_POST_RESULT 和一个表示任务执行结果的 AsyncTaskResult 对象。这个 sHandler 对象是 InternalHandler 类的一个实例,那么这条消息肯定会在InternalHandler 的 handleMessage() 方法中被处理。InternalHandler 的源码如下所示:
// 静态类private static class InternalHandler extends Handler { //... @Override public void handleMessage(Message msg) { AsyncTaskResult result = (AsyncTaskResult) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: // 调用 finish 方法 result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: // 解释 publishProgress() 方法可以从子线程切换到UI线程 result.mTask.onProgressUpdate(result.mData); break; } } }
发现 Handler 是一个静态类。是为了能够将执行环境从子线程切换到主线程中。由于静态类的关系,更进一步要求 AsyncTask 的类必须在主线程中加载(这里不是很明白。静态成员会在加载类的时候进行初始化,这样同一个进程中的线程就可以共享资源了?)。sHandler 在收到 MESSAGE_POST_RESULT 后会调用 fininsh() 方法:
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } // 状态标示 mStatus = Status.FINISHED; }
这里就比较简单了。分取消与否调用 onCancelled() 和 onPostExecute() 方法。
四. 总结分析
AsyncTask 封装了 Thread 和 Handler, 通过 AsyncTask 可以更加方便的执行后台任务以及在主线程中访问 UI,但是 AsyncTask 并不适合进行特别耗时的后台任务。建议采用线程池。
提供了四个核心方法:
onPreExecute():在主线程中执行,在异步任务执行前,此方法会被调用,用于做一些准备工作。
doInBackground(Params...params):在线程池中执行,此方法用于执行异步任务。
onProgressUpdate(Progress...value):在主线程中执行,当后台任务的执行进度发生变化时,此方法会被调用。
onPostExecute(Result result):在主线程中执行,在异步任务执行之后,此方法会被调用。
onCancelld():当异步任务被取消时,该方法会被调用。
对于使用AsyncTask 有以下几点需要注意的:
AsyncTask 的类必须在主线程中加载。
AsyncTask 的 对象 必须在 主线程中创建。
execute 必须在 UI 线程中调用。
不要在程序中直接调用上述前四个方法。
在 Android 1.6 之前 AT 是串行执行任务的(可同时执行 5 个任务),那时候采用线程池来处理并行任务,但是从 3.0 开始为了避免 AT 所带来的并发错误,AT 又采用一个线程来串行执行任务(只能执行 1 个任务)。尽管如此,3.0 之后仍然可以通过 AT 的 executeOnExecutor 方法来并行执行任务。对于这一点的实验可以看 《Android 开发艺术探索》P403。