AsyncTask简介(一) 基本概念和用法

1 AsyncTask

在Android中,由于不能在子线程更新UI,也不能在主线程(UI线程)做耗时操作。所以经常需要在后台进行操作并在操作完成以后将结果发送给UI线程。Android为了开发者提供了Handler+后台Thread的开发框架,用于上述场景。关于Handler的介绍可以参见:Handler,Message,Looper & MessageQueue 。Handler的使用稍显复杂,需要继承Handler类,重写处理函数。还需要开发者自己开辟线程,在线程中完成操作以后再发送消息将结果更新到UI线程。Android为我们提供了AsyncTask这个工具类。AsyncTask对Handler+Thread开发框架进行了封装,AysncTask可以帮助我们创建后台线程,开发者只需要简单的重写几个回调函数,就可以完成后台子线程处理耗时操作,再将结果更新到UI线程的操作。
在后续的章节先学习AsyncTask的基本用法,以及使用AsyncTask的注意点。
下一篇文章AsyncTask简介(二) 源码剖析 将从AsyncTask的源码入手,分析AsyncTask的原理。

2 AsyncTask的基本用法

2.1 AsyncTask的定义

AsyncTask的类声明:

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

这个声明向我们传达了两个信息
1. AsyncTask是一个泛型类,有三个泛型类型:
  a) Params: 要执行的任务的参数的类型
  b) Progress:任务在后台执行的进度单元的类型
  c) Result:返回值的类型
2. 同时AsyncTask还是一个抽象类,必须继承以后才能使用

使用AsyncTask,可以重写四个回调函数

//在UI线程调用,在doInBackground之前执行
protected void onPreExecute()   
//在后台线程调用,完成后台任务的方法主体,抽象方法,AsyncTask的子类必须重写该方法
protected abstract Result doInBackground(Params... params)  
//在UI线程调用,显示操作进度   
protected void onProgressUpdate(Progress... values)  
//在UI线程调用,在doInBackground之后执行  
protected void onPostExecute(Result result)    

下面我们就结合一个Demo对AsyncTask的类型参数和回调的使用进行说明。

2.2 使用doInBackground方法的AsyncTask

这个Demo很简单,在主Activity中,有一个Button,一个ProgressBar,一个TextView。点击Button,会执行下载任务,用ProgressBar显示下载的进度,下载完成后,在TextView上更新Task End,告诉用户下载完毕。

开始下载 ——> 下载结束

public class MainActivity extends ActionBarActivity {
    private static final String TAG = "MainActivity";
    private static final int PROGRESS_BAR_MAX_VALUE = 5;
    private static final int PORGRESS_UPDATE_TIME_GAP = 100;

    private Button mButton;
    private ProgressBar mProgressBar;
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mButton = (Button)findViewById(R.id.button);
        mProgressBar = (ProgressBar)findViewById(R.id.progressBar);
        mTextView = (TextView)findViewById(R.id.textView);

        mButton.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {
                AsyncTask<Integer, Integer, String> asyncTask = new AsyncTask<Integer, Integer, String>() {

                    @Override
                    protected void onPreExecute() {
                        Log.d(TAG, "onPreExecute was executed in : " + Thread.currentThread().getName());
                        mProgressBar.setProgress(0);
                        mProgressBar.setMax(PROGRESS_BAR_MAX_VALUE);
                        mTextView.setText("Task Start");
                    }

                    @Override
                    protected String doInBackground(Integer... params) {
                        Log.d(TAG, "doInBackground was executed in : " + Thread.currentThread().getName());
                        try {
                            for (int i = 0 ; i <= params[0]; i++ ) {
                                Thread.sleep(params[1]);
                                this.publishProgress(i);
                                Log.d(TAG, "publishProgress was executed in : " + Thread.currentThread().getName());
                            }

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return new String("Task End");
                    }

                    @Override
                    protected void onProgressUpdate(Integer... values) {
                        Log.d(TAG, "onProgressUpdate was executed in : " + Thread.currentThread().getName());
                        mProgressBar.setProgress(values[0]);
                    }

                    @Override
                    protected void onPostExecute(String result) {
                        Log.d(TAG, "onPostExecute was executed in : " + Thread.currentThread().getName() + ". the result is :" + result);
                        mTextView.setText(result);
                    }   
                };  
                asyncTask.execute(PROGRESS_BAR_MAX_VALUE, PORGRESS_UPDATE_TIME_GAP);
            }
        });
    }
}

2.2.1 泛型参数

AsyncTask的三个泛型类型,在Demo代码中分别对应Integer, Integer,String。

AsyncTask<Params, Progress, Result> execute(Params... params)   ->  AsyncTask<Integer, Integer, String> execute(Integer... params)
Result doInBackground(Params... params) ->  String doInBackground(Integer... params)
void onProgressUpdate(Progress... values)   ->  void onProgressUpdate(Integer... values)
void onPostExecute(Result result)   ->  void onPostExecute(String result)

所以在Demo中执行任务的参数类型是Integer,显示进度的参数类型是Integer,后台任务的返回值的参数类型是String。

2.2.2 AsyncTask回调函数的执行步骤

图是说明顺序和逻辑最好的工具。下面这张图显示了这几个函数执行的顺序和执行的宿主线程。

    这里写图片描述

如图所示:
1. 开发者主动调用execute
2. onPreExecute将会在主线程中被调用
3. doInBackground将会在后台线程中被调用,doInBackground过程调用publishProgress,会出发onUpdateProgress在主线中被调用
4. onPostExecute在主线程中被调用。
下面我们对每一步都进行详细的说明。
Step 1,开始AsyncTask:
点击Button调用AsyncTask的execute(Integer… params),这是开发者唯一需要主动调用的函数,且需要在UI线程中调用。表示开始执行AsyncTask。
需要说明的是,将来doInBackground函数在执行的参数列表正是在执行execute函数时传递的参数列表。
在Demo中,我们向execute传递了两个整形参数:PROGRESS_BAR_MAX_VALUE,PORGRESS_UPDATE_TIME_GA。AsyncTask会帮助我们将这两个参数传递给doInBackground。

asyncTask.execute(PROGRESS_BAR_MAX_VALUE, PORGRESS_UPDATE_TIME_GAP);

Step 2,AsyncTask的准备工作:
onPreExecute(),在调用execute以后立刻执行,对任务进行相关的初始设置。由于其会在主线程中被调用,所以可以用来做一些UI上的准备工作。在Demo中通过该回调将progressBar的值置为0,将TextView置为“Task Start”,表示下载任务即将开始。

protected void onPreExecute() {
    Log.d(TAG, "onPreExecute was executed in : " + Thread.currentThread().getName());
    mProgressBar.setProgress(0);
    mProgressBar.setMax(PROGRESS_BAR_MAX_VALUE);
    mTextView.setText("Task Start");
}

Step 3, 后台线程任务运行:
doInBackground(Params… params),当准备工作(onPreExecute)执行完以后,就轮到正式的任务(doInBackground)开始了。
doInBackground的参数列表Params… params 对应 step1 中调用的execute的参数列表。所以params[0]和params[1]正是step1中调用execute传入的PROGRESS_BAR_MAX_VALUE和PORGRESS_UPDATE_TIME_GAP。Demo中的下载任务(用sleep函数模拟)就是在这里被运行的。在下载任务执行过程中,总共需要PROGRESS_BAR_MAX_VALUE步,每隔PORGRESS_UPDATE_TIME_GAP毫秒完成一步,主动调用一次pulishProgress,通知主线程调用onProgressUpdate函数,更新Activity中的下载进度条的进度。

protected String doInBackground(Integer... params) {
    Log.d(TAG, "doInBackground was executed in : " + Thread.currentThread().getName());
    try {
        for (int i = 0 ; i <= params[0]; i++ ) {
            Thread.sleep(params[1]);
            this.publishProgress(i);
            Log.d(TAG, "publishProgress was executed in : " + Thread.currentThread().getName());
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
        return new String("Task End");
}

在step3中提到了另外两个函数:
a) publishProgress(Progress… values)
b) onProgressUpdate(Progress… values)
下面是他们的关系图

    这里写图片描述
 

onProgressUpdate和publishProgress是成对出现的。在doInBackground中(即子线程中)调用一次publishProgress,主线程就会调用一次onProgressUpdate。因此onProgressUpdate严格意义上来说并不是执行AsyncTask过程的中的某个步骤,而是由后台线程的doInBackground主动通知主线程调用的回调函数,是和doInBackground交织在一起的。
Step4:任务完成,通知主线程结果
onPostExecute(Result result)。在doInBackground执行完成以后,需要将执行结果通知到主线程,onPostExecute将在主线程被调用,而其参数(Result result)正是doInBackground的返回值。

protected void onPostExecute(String result) {
    Log.d(TAG, "onPostExecute was executed in : " + Thread.currentThread().getName() + ". the result is :" + result);
    mTextView.setText(result);
}

在Demo中,Result的参数类型是String,所以doInBackground的返回值类型和onPostExcute的参数类型都是String。Step3中doInBackground返回的字串是”Task End”,onPostExecute将会获得这个字串,并更新至UI。

2.2.3 从Log来看执行顺序

我们在Demo中的每一个回调中添加了Log,运行Demo,打印出如下log:

D/MainActivity(20029): onPreExecute was executed in : main
D/MainActivity(20029): doInBackground was executed in : AsyncTask #2
D/MainActivity(20029): publishProgress was executed in : AsyncTask #2
D/MainActivity(20029): onProgressUpdate was executed in : main
D/MainActivity(20029): publishProgress was executed in : AsyncTask #2
D/MainActivity(20029): onProgressUpdate was executed in : main
D/MainActivity(20029): publishProgress was executed in : AsyncTask #2
D/MainActivity(20029): onProgressUpdate was executed in : main
D/MainActivity(20029): publishProgress was executed in : AsyncTask #2
D/MainActivity(20029): onProgressUpdate was executed in : main
D/MainActivity(20029): publishProgress was executed in : AsyncTask #2
D/MainActivity(20029): onProgressUpdate was executed in : main
D/MainActivity(20029): publishProgress was executed in : AsyncTask #2
D/MainActivity(20029): onProgressUpdate was executed in : main
D/MainActivity(20029): onPostExecute was executed in : main

Log也很好的体现了前面流程图中每个回调函数执行的顺序,以及他们执行的宿主线程。

2.2 AsyncTask的简易用法

AsyncTask还提供一个简单易用的使用方法,无需创建AsyncTask实例,使用AsyncTask的静态函数

public static void execute(Runnable runnable)

调用execute方法直接执行一个Runnable对象。下面是这种方法的Demo:

public class MainActivity extends ActionBarActivity {
    protected static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AsyncTask.execute(new Runnable() {
            public void run() {
                try {
                        Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(TAG, "run method was executed in : " + Thread.currentThread().getName());     
            }
        });
    }
}

这个Demo非常简单,
1. 定义一个Runnable对象,在run方法中休眠200ms,然后打印出当前线程名。
2. 调用AsyncTask.execute方法执行这个Runnable对象。
运行Demo以后,打印出Log:

D/MainActivity(27481): run method was executed in : AsyncTask #2

注意到打印出的调用run方法的线程名是AsyncTask #2,这说明Runnable对象在后台线程被调用。
与execute( Params… params )方法相比,该方法非常简短易用。但是无onPreExecute,onPostExecute,onProgressUpdate等回调。当应用程序需要用一个后台线程执行一个简短的程序,并且不关心执行进度,执行结果。可以使用execute(Runnable runnable)。

3 使用AsyncTask需要遵守的几个准则:

  1. execute函数必须在UI上调用。
  2. 不要手动调用onPreExecute,onPostExecute,doInBackground,onProgressUpdate等回调函数。
  3. AsyncTask可以保证回调函数的调用都是同步的,不需要额外的同步处理。
  4. AsyncTask适合简短的操作,不要在AsyncTask中做过分的耗时操作。

4 总结

至此我们已经将AsyncTask的类型参数,回调的执行步骤,使用AsyncTask的注意点都讲解了一遍。讲到这里,相信大家都有点儿晕,那么多的泛型参数,那么多的回调,有的回调在子线程中调用,有的回调在主线程调用,有的在前,有的在后,有的交叉调用。怎么去记?
下一篇文章AsyncTask简介(二) 源码剖析将会带领大家剖析额AsyncTask的的源码,了解AsyncTask是如何设计的,并且了解AsyncTask为什么是这样设计的。到时候再来记这些回调的执行的顺序,执行的宿主线程就不再困难了。
源码之前,了无秘密。 –侯杰

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页