Android 入门 -- AsyncTask

异步任务机制(AsyncTask),目的是解决 Android 中不能在主线程中执行耗时操作的问题,由于 Android 系统的特性,如果在主线程中执行耗时操作,将会导致界面卡顿无法及时响应,容易引起 ANR(Application Not Responding)。在 Android 开发中一般会将耗时操作放在子线程中执行,比如网络请求,文件读写操作等,子线程执行完成后再通知主线程更新界面。因此便有了异步任务的概念,主线程在某个时刻发送一个耗时任务交给子线程,然后继续运行主线程的内容,子线程启动后便开始执行耗时任务,执行完成后通知主线程,主线程收到通知后就处理这个通知,没有收到则继续做自己的事。

在Android中实现异步任务机制有很多中方式,比如 ThreadAsyncTaskIntentService,和Handler,本文主要描述 AsyncTask 的实现。

AsyncTask 是一种轻量级的异步任务类, 它的实现原理是对 Thread 和 Handler 进行封装,方便开发者在子线程中更新 UI。使用它可以在后台执行耗时任务,并将执行进度和最终结果返回给主线程,可以在主线程中处理执行进度和最终结果,下面开始实现一个简单的异步任务类

AsyncTask 类的定义:

1public abstract class AsyncTask<Params, Progress, Result> {} 2

三种泛型类型含义如下

  • Params : 启动任务需要的参数类型
  • Progress : 后台任务执行的进度类型,一般是 Integer
  • Result : 后台运行结果的类型

如果不需要传入参数或者没有返回值,则可以用 java.lang.Void 类型代替(注意这里是 Void 类,而不是 void )

实现一个异步任务类

编写一个自定义的异步任务类时,首先需要继承 AsyncTask 类,然后需要重写一些方法,常用方法描述如下:

  • onPreExecute() : 在主线程中执行,任务开始前被调用,一般用于为异步任务做准备,比如,在执行异步任务时需要有一个提示框来显示,则可以在这里调用显示提示框的代码

  • doInBackground(Params… params) : 在后台执行,在onPreExecute()完成后立即执行,用于执行耗时任务,在这里不能直接操作 UI 界面,此方法将接收输入参数和返回运行结果。在执行过程中可以调用publishProgress(Progress… values)来更新进度信息。

  • onProgressUpdate(Progress… values) : 在主线程中执行,通过在 doInBackground() 中调用 publishProgress(Progress… values),就会调用此方法,传入的参数即为当前进度值。

  • onPostExecute(Result result) : 在主线程中执行,后台运行结束后的返回值将作为此方法的参数传入,并开始调用此方法

一个异步任务类至少要重写这两个方法:doInBackground(Params… params)和onPostExecute(Result result);

使用异步任务类

在使用的时候,类似 Thread 的操作,需要 new 一个该类的实例,并调用其 execute(Params…params) 方法,其中的参数将会传递给 doInBackground(Params…params) 方法

使用时需要注意一下几点:

  • 异步任务类的实例必须在主线程中创建。
  • execute(Params… params) 方法必须在主线程中调用。
  • 不能在doInBackground(Params… params)中更改UI组件的信息。
  • 一个异步任务的实例只能执行一次,如果执行第二次将会抛出异常。

实例代码

下面是对异步任务类的一个简单实现,需求是实现一个异步下载图片并显示下载进度和将下载的图片显示到界面上的功能,具体代码如下

添加权限

要下载图片,首先需要添加网络访问的权限

1<uses-permission android:name="android.permission.INTERNET"/> 2

界面布局

根据需求,布局界面需要有一个 ImageView 显示图片,并且需要一个 ProgressBar 显示下载进度,最后需要一个 Button 启动异步任务,于是布局界面代码如下

activity_asynctask.xml

1<?xml version="1.0" encoding="utf-8"?> 2<LinearLayout 3 xmlns:android="http://schemas.android.com/apk/res/android" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:gravity="center_horizontal" 8 android:orientation="vertical" 9 tools:context=".AsyncTaskActivity"> 10 11 <ImageView 12 android:id="@+id/iv_image" 13 android:layout_width="100dp" 14 android:layout_height="100dp" 15 android:layout_marginTop="16dp" 16 android:background="#444444" 17 android:contentDescription="@string/app_name"/> 18 19 <TextView 20 android:id="@+id/tv_progress_value" 21 android:layout_gravity="right" 22 android:layout_marginRight="16dp" 23 android:layout_marginTop="16dp" 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 android:textSize="12sp" 27 android:text="0%"/> 28 29 <ProgressBar 30 android:layout_marginRight="16dp" 31 android:layout_marginLeft="16dp" 32 style="?android:attr/progressBarStyleHorizontal" 33 android:id="@+id/pb_download_progress" 34 android:layout_width="match_parent" 35 android:layout_height="wrap_content"/> 36 37 <Button 38 android:layout_marginTop="16dp" 39 android:id="@+id/bt_download" 40 android:layout_width="wrap_content" 41 android:layout_height="wrap_content" 42 android:text="点击下载"/> 43 44</LinearLayout> 45

编写自定义异步任务类

如前面所述,要实现一个自定义的异步任务,需要继承 AsyncTask 类,并且实现一些方法,具体代码如下:

1 /** 2 * 三个参数分别指定了三个重写方法传入的参数,如果没有则使用 Void 3 * doInBackground(String... params) ---- String,返回值将作为 onPostExecute() 的参数 4 * onProgressUpdate(Integer... values) ---- Integer 5 * onPostExecute(byte[] bytes) ---- byte[] 6 */ 7 class DownloadTask extends AsyncTask<String, Integer, byte[]> { 8 9 /** 10 * 在 UI 线程中执行,任务执行前调用 11 */ 12 @Override 13 protected void onPreExecute() { 14 super.onPreExecute(); 15 } 16 17 /** 18 * 在线程池中执行,用于执行异步任务,这里执行了一个下载过程 19 */ 20 @Override 21 protected byte[] doInBackground(String... params) { 22 23 //执行下载,传入的第一个参数是 url 24 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 25 try { 26 URL mUrl = new URL(params[0]); 27 HttpURLConnection conn = (HttpURLConnection) mUrl.openConnection(); 28 29 conn.setDoInput(true); 30 conn.connect(); 31 32 int totalSize = conn.getContentLength();//获取连接的数据总量 33 int readSize = 0; 34 35 int len; 36 37 InputStream is = conn.getInputStream(); 38 byte[] data = new byte[1024]; 39 while ((len = is.read(data)) != -1) { 40 readSize += len; 41 int progressValue = (int) ((readSize / (float) totalSize) * 100); 42 43 publishProgress(progressValue);//更新进度 44 bos.write(data, 0, len); 45 Thread.sleep(100); 46 } 47 is.close(); 48 bos.close(); 49 conn.disconnect(); 50 51 } catch (Exception e) { 52 e.printStackTrace(); 53 } 54 return bos.toByteArray(); 55 } 56 57 /** 58 * 在 UI 线程中执行,当后台任务的执行进度发生改变时被调用,更新进度 59 */ 60 @Override 61 protected void onProgressUpdate(Integer... values) { 62 super.onProgressUpdate(values); 63 progressBar.setProgress(values[0]); 64 tvProgressValue.setText(values[0] + "%"); 65 } 66 67 /** 68 * 在 UI 线程中执行,异步任务执行完成后调用 69 */ 70 @Override 71 protected void onPostExecute(byte[] bytes) { 72 super.onPostExecute(bytes); 73 //填充 ImageView 74 ivImage.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.length)); 75 btDownload.setEnabled(true); 76 } 77 } 78

可以看到,这里主要的逻辑是在 doInBackground() 中,执行了一个网络下载的过程,并使用publishProgress() 方法来更新下载进度,最后将下载的结果 byte[] 传给 onPostExecute() 用于显示在 ImageView 中。

使用异步任务类

使用就比较简单了,如下:

1 btDownload.setOnClickListener(new View.OnClickListener() { 2 @Override 3 public void onClick(View v) { 4 btDownload.setEnabled(false); 5 new DownloadTask().execute(IMAGE_URL);//传入图片下载链接,开始执行异步任务 6 } 7 }); 8

到此,一个完整的使用异步任务的过程就完成了,下面是运行效果:

下面详细介绍一下AsyncTask的执行原理。
先看一下AsyncTask的大纲视图:
我们可以看到关键几个步骤的方法都在其中,doInBackground(Params… params)是一个抽象方法,我们继承AsyncTask时必须覆写此方法;onPreExecute()、onProgressUpdate(Progress… values)、onPostExecute(Result result)、onCancelled()这几个方法体都是空的,我们需要的时候可以选择性的覆写它们;publishProgress(Progress… values)是final修饰的,不能覆写,只能去调用,我们一般会在doInBackground(Params… params)中调用此方法;另外,我们可以看到有一个Status的枚举类和getStatus()方法,Status枚举类代码段如下:

1 //初始状态 2 private volatile Status mStatus = Status.PENDING; 3 4 public enum Status { 5 /** 6 * Indicates that the task has not been executed yet. 7 */ 8 PENDING, 9 /** 10 * Indicates that the task is running. 11 */ 12 RUNNING, 13 /** 14 * Indicates that {@link AsyncTask#onPostExecute} has finished. 15 */ 16 FINISHED, 17 } 18 19/** 20 * Returns the current status of this task. 21 * 22 * @return The current status. 23 */ 24 public final Status getStatus() { 25 return mStatus; 26 } 27

可以看到,AsyncTask的初始状态为PENDING,代表待定状态,RUNNING代表执行状态,FINISHED代表结束状态,这几种状态在AsyncTask一次生命周期内的很多地方被使用,非常重要。
介绍完大纲视图相关内容之后,接下来,我们会从execute(Params… params)作为入口,重点分析一下AsyncTask的执行流程,我们来看一下execute(Params… params)方法的代码段:

1public final AsyncTask<Params, Progress, Result> execute(Params... params) { 2 if (mStatus != Status.PENDING) { 3 switch (mStatus) { 4 case RUNNING: 5 //如果该任务正在被执行则抛出异常 6 //值得一提的是,在调用cancel取消任务后,状态仍未RUNNING 7 throw new IllegalStateException("Cannot execute task:" 8 + " the task is already running."); 9 case FINISHED: 10 //如果该任务已经执行完成则抛出异常 11 throw new IllegalStateException("Cannot execute task:" 12 + " the task has already been executed " 13 + "(a task can be executed only once)"); 14 } 15 } 16 17 //改变状态为RUNNING 18 mStatus = Status.RUNNING; 19 20 //调用onPreExecute方法 21 onPreExecute(); 22 23 mWorker.mParams = params; 24 sExecutor.execute(mFuture); 25 26 return this; 27 } 28

代码中涉及到三个陌生的变量:mWorker、sExecutor、mFuture,我们也会看一下他们的庐山真面目:
关于sExecutor,它是java.util.concurrent.ThreadPoolExecutor的实例,用于管理线程的执行。代码如下:

1private static final int CORE_POOL_SIZE = 5; 2 private static final int MAXIMUM_POOL_SIZE = 128; 3 private static final int KEEP_ALIVE = 10; 4 5//新建一个队列用来存放线程 6 private static final BlockingQueue<Runnable> sWorkQueue = 7 new LinkedBlockingQueue<Runnable>(10); 8//新建一个线程工厂 9 private static final ThreadFactory sThreadFactory = new ThreadFactory() { 10 private final AtomicInteger mCount = new AtomicInteger(1); 11 //新建一个线程 12 public Thread newThread(Runnable r) { 13 return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); 14 } 15 }; 16//新建一个线程池执行器,用于管理线程的执行 17 private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, 18 MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory); 19

mWorker实际上是AsyncTask的一个的抽象内部类的实现对象实例,它实现了Callable接口中的call()方法,代码如下:

1private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { 2 Params[] mParams; 3 } 4

而mFuture实际上是java.util.concurrent.FutureTask的实例,下面是它的FutureTask类的相关信息:

1/** 2 * A cancellable asynchronous computation. 3 * ... 4 */ 5public class FutureTask<V> implements RunnableFuture<V> { 6
1public interface RunnableFuture<V> extends Runnable, Future<V> { 2 /** 3 * Sets this Future to the result of its computation 4 * unless it has been cancelled. 5 */ 6 void run(); 7} 8

可以看到FutureTask是一个可以中途取消的用于异步计算的类。

下面是mWorker和mFuture实例在AsyncTask中的体现:

1 private final WorkerRunnable<Params, Result> mWorker; 2 private final FutureTask<Result> mFuture; 3 4public AsyncTask() { 5 mWorker = new WorkerRunnable<Params, Result>() { 6 //call方法被调用后,将设置优先级为后台级别,然后调用AsyncTask的doInBackground方法 7 public Result call() throws Exception { 8 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 9 return doInBackground(mParams); 10 } 11 }; 12 13 //在mFuture实例中,将会调用mWorker做后台任务,完成后会调用done方法 14 mFuture = new FutureTask<Result>(mWorker) { 15 @Override 16 protected void done() { 17 Message message; 18 Result result = null; 19 20 try { 21 result = get(); 22 } catch (InterruptedException e) { 23 android.util.Log.w(LOG_TAG, e); 24 } catch (ExecutionException e) { 25 throw new RuntimeException("An error occured while executing doInBackground()", 26 e.getCause()); 27 } catch (CancellationException e) { 28 //发送取消任务的消息 29 message = sHandler.obtainMessage(MESSAGE_POST_CANCEL, 30 new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null)); 31 message.sendToTarget(); 32 return; 33 } catch (Throwable t) { 34 throw new RuntimeException("An error occured while executing " 35 + "doInBackground()", t); 36 } 37 38 //发送显示结果的消息 39 message = sHandler.obtainMessage(MESSAGE_POST_RESULT, 40 new AsyncTaskResult<Result>(AsyncTask.this, result)); 41 message.sendToTarget(); 42 } 43 }; 44 } 45

我们看到上面的代码中,mFuture实例对象的done()方法中,如果捕捉到了CancellationException类型的异常,则发送一条“MESSAGE_POST_CANCEL”的消息;如果顺利执行,则发送一条“MESSAGE_POST_RESULT”的消息,而消息都与一个sHandler对象关联。这个sHandler实例实际上是AsyncTask内部类InternalHandler的实例,而InternalHandler正是继承了Handler,下面我们来分析一下它的代码:

1private static final int MESSAGE_POST_RESULT = 0x1; //显示结果 2 private static final int MESSAGE_POST_PROGRESS = 0x2; //更新进度 3 private static final int MESSAGE_POST_CANCEL = 0x3; //取消任务 4 5 private static final InternalHandler sHandler = new InternalHandler(); 6 7private static class InternalHandler extends Handler { 8 @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) 9 @Override 10 public void handleMessage(Message msg) { 11 AsyncTaskResult result = (AsyncTaskResult) msg.obj; 12 switch (msg.what) { 13 case MESSAGE_POST_RESULT: 14 // There is only one result 15 //调用AsyncTask.finish方法 16 result.mTask.finish(result.mData[0]); 17 break; 18 case MESSAGE_POST_PROGRESS: 19 //调用AsyncTask.onProgressUpdate方法 20 result.mTask.onProgressUpdate(result.mData); 21 break; 22 case MESSAGE_POST_CANCEL: 23 //调用AsyncTask.onCancelled方法 24 result.mTask.onCancelled(); 25 break; 26 } 27 } 28 } 29

我们看到,在处理消息时,遇到“MESSAGE_POST_RESULT”时,它会调用AsyncTask中的finish()方法,我们来看一下finish()方法的定义:

1private void finish(Result result) { 2 if (isCancelled()) result = null; 3 onPostExecute(result); //调用onPostExecute显示结果 4 mStatus = Status.FINISHED; //改变状态为FINISHED 5 } 6

原来finish()方法是负责调用onPostExecute(Result result)方法显示结果并改变任务状态的啊。
另外,在mFuture对象的done()方法里,构建一个消息时,这个消息包含了一个AsyncTaskResult类型的对象,然后在sHandler实例对象的handleMessage(Message msg)方法里,使用下面这种方式取得消息中附带的对象:

1AsyncTaskResult result = (AsyncTaskResult) msg.obj; 2

这个AsyncTaskResult究竟是什么呢,它又包含什么内容呢?其实它也是AsyncTask的一个内部类,是用来包装执行结果的一个类,让我们来看一下它的代码结构:

1@SuppressWarnings({"RawUseOfParameterizedType"}) 2private static class AsyncTaskResult<Data> { 3 final AsyncTask mTask; 4 final Data[] mData; 5 6 AsyncTaskResult(AsyncTask task, Data... data) { 7 mTask = task; 8 mData = data; 9 } 10} 11

看以看到这个AsyncTaskResult封装了一个AsyncTask的实例和某种类型的数据集,我们再来看一下构建消息时的代码:

1//发送取消任务的消息 2message = sHandler.obtainMessage(MESSAGE_POST_CANCEL, 3 new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null)); 4message.sendToTarget(); 5
1//发送显示结果的消息 2message = sHandler.obtainMessage(MESSAGE_POST_RESULT, 3 new AsyncTaskResult<Result>(AsyncTask.this, result)); 4message.sendToTarget(); 5

在处理消息时是如何使用这个对象呢,我们再来看一下:

1result.mTask.finish(result.mData[0]); 2
1result.mTask.onProgressUpdate(result.mData); 2

概括来说,当我们调用execute(Params… params)方法后,execute方法会调用onPreExecute()方法,然后由ThreadPoolExecutor实例sExecutor执行一个FutureTask任务,这个过程中doInBackground(Params… params)将被调用,如果被开发者覆写的doInBackground(Params… params)方法中调用了publishProgress(Progress… values)方法,则通过InternalHandler实例sHandler发送一条MESSAGE_POST_PROGRESS消息,更新进度,sHandler处理消息时onProgressUpdate(Progress… values)方法将被调用;如果遇到异常,则发送一条MESSAGE_POST_CANCEL的消息,取消任务,sHandler处理消息时onCancelled()方法将被调用;如果执行成功,则发送一条MESSAGE_POST_RESULT的消息,显示结果,sHandler处理消息时onPostExecute(Result result)方法被调用。
经过上面的介绍,相信朋友们都已经认识到AsyncTask的本质了,它对Thread+Handler的良好封装,减少了开发者处理问题的复杂度,提高了开发效率,希望朋友们能多多体会一下。

代码交流 2021