AsyncTask是什么
AsyncTask
是用来让我们在UI线程中调用以进行一些异步任务的类,通常要求这个异步任务的持续时间很短,最多持续几秒钟,在完成后台任务时,会将结果通知到主线程进行处理。
说的更本质一些,AsyncTask
=Thread
+Handler
,Thread
用来进行后台任务,Handler
用于处理UI线程的消息,例如任务完成后更新视图操作等。
AsyncTask类从apiLevel=3开始才有。
AsyncTask基本用法
Step 1
实现一个自己的类继承自:class AsyncTask<Params, Progress, Result>
三个泛型分别表示:
Params
:表示执行任务的参数,可以传入多个Progress
:包含耗时操作的进度信息,用于界面的显示更新Result
:耗时任务的执行结果
对于不需要的内容可以使用Void
类型。
Step 2
实现其中的抽象方法:protected abstract Result doInBackground(Params... params)
方法,该方法中就是我们需要进行的耗时任务,如网络查询操作等,然后将操作的结果进行返回。
该方法可以理解为Thread
类中的run
方法。
Step 3
Step 2执行完成后,AsyncTask
将会调用protected void onPostExecute(Result result)
方法,参数就是我们返回的Result,该方法在UI线程中执行,用于更新界面显示,不强制要求实现。
Step 4
实例化我们自定义的任务类,并执行。
MyAsyncTask task = new MyAsyncTask();
task.execute(param1, param2, ...);
通过以上代码可以实现我们的需求,但是有一点不好的是,调用execute
方法在不同的Android版本上的表现不太一样:
switch(apiLevel)
apiLevel = 3 : 单线程执行
apiLevel > 3 and apiLevel < 11 : 多线程执行
apiLevel >= 11 : 单线程执行
所以应该尽量避免这种有歧义的代码,在执行时我们需要指定任务的执行方式,单线程和多线程的执行代码示例如下(apiLevel>=11):
task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, param1, param2, ...);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, param1, param2, ...);
这里的单线程表示在当前APP进程中,所有的异步任务都将串行执行,即执行完一个再执行后一个,而多线程就是指我们可以同时运行多个异步任务的后台操作。大家都知道多线程的好处就是可以提高运行效率,但是却也带来了同步的问题,因此这两种方法都有存在的必要。
变长参数列表param1, param2, ...
在调用时没有需求可以不写,其本质就是一个数组。
PS
完整的一次异步任务中方法的执行顺序是:
1. onPreExecute
:UI线程
2. doInBackground
:子线程
3. onPostExecute
:UI线程
还有一个关于进度显示的方法protected void onProgressUpdate(Progress... values)
,该方法运行在UI线程,可以在任意时机被调用,而何时被调用则是取决于我们调用protected final void publishProgress(Progress... values)
的时机。
我们可以在任意时间,任意线程中publishProgress
,不过在主线程中调用似乎没有什么必要,onPreExecute
表示任务还没开始,此时进度为0,onPostExecute
此时任务已经完成,进度为已满的状态。
AsyncTask用对了吗~?
1. 任务只给执行一次~
我们的每个任务示例只可以被execute一次,如果多次调用被抛出IllegalStateException
异常,因此任务执行完成后请及时赋值null
。
AsyncTask共有3种状态:
PENDING
每个实例构造完成的初始值RUNNING
表示使用者调用了executeOnExecutor或execute,此时会立刻调用onPreExecute方法,但是不会立即执行doInBackground方法FINISHED
表示onPostExecute方法执行完成
在任务执行时需要判断当前Task是否是PENDING
状态,不是的话将抛出异常,每个Task类只可以被执行一次。
2. 不要在子线程中操作主线程中的数据
在子线程中(doInBackground
方法)更新视图应该没人会做,但是却会有很多情况下在子线程中更新视图中的数据。
比较常见的情况是修改ListView
中的数据,例如下面的这种代码:
// doInBackground方法中
// TODO 获取数据操作...
// mDataList表示ListView加载用到的数据
mDataList.removeAll();
mDataList.addAll(新获取的数据);
写出这样的代码可能会造成在滑动列表时产生异常:IndexOutOfBoundsException
,而且即使没有抛异常也不代表加载显示的数据正确的。在这种情况下的推荐做法是,将后台进行的耗时操作的结果数据返回(泛型Result
,在本例中可以定义为ArrayList<String>
),并在onPostExecute
方法中对该数据进行展示,保证所有和UI更新的代码都在UI线程中执行。
3. 适当并及时地终止任务
AsyncTask
主要用途就是异步获取数据,并用于页面加载,所以当页面不存在时,就应该适当地终止任务以节约资源。
例如在一个Activity
调用onStart
时,我们需要从服务端获取最新数据用于界面显示,此时我们需要execute一个AsyncTask
,在onPostExecute
方法中我们会写一些关于页面更新的逻辑。
但是当Activity
调用了onStop
方法,此时页面已经变得不可见,我们也并不希望有网络数据返回时再去更新界面显示,因此在此时我们就需要调用AsyncTask
的cancel
方法去取消任务。
如果当前任务还处于运行
RUNNING
状态,调用cancel
方法后将不会执行onPostExecute
方法,并且在doInBackground
方法中也可以通过函数isCancelled
来随时获取当前任务的状态,如果返回true
,我们可以不进行接下来的操作(例如执行多个网络请求,在每个请求执行之前判断来决定是否要继续执行)。
举个栗子
通过一个简单的例子来了解一下AsyncTask
类的具体使用方法:
public class TaskActivity extends Activity {
AsyncTask<Void, Integer, ArrayList<String>> mTask;
@Override
protected void onStart() {
super.onStart();
startTask();
}
@Override
protected void onStop() {
super.onStop();
cancelTask();
}
// 开始任务
private void startTask() {
if (mTask != null) {
mTask.cancel(true);
}
mTask = new MyAsyncTask();
mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
// 取消任务
private void cancelTask() {
if (mTask != null) {
mTask.cancel(true);
mTask = null;
}
}
/**
* 自定义异步任务类
*/
private class MyAsyncTask extends AsyncTask<Void, Integer, ArrayList<String>> {
@Override
protected void onPreExecute() {
// TODO 在这里可以加载LoadingView提示用户
}
@Override
protected ArrayList<String> doInBackground(Void... voids) {
// TODO 在这里执行耗时的操作,如网络请求,大量的计算等等
ArrayList<String> result = new ArrayList<String>();
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
publishProgress(i);
result.add("" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
@Override
protected void onPostExecute(ArrayList<String> result) {
mTask = null;
// TODO 在这里进行相应的界面更新逻辑
// 例如将result加载到ListView中进行显示
}
@Override
protected void onProgressUpdate(Integer... progress) {
// TODO 将进度更新到界面,本例用用整数表示百分比
}
}
}
以上的代码实现了一个自定义的异步任务类MyAsyncTask
,该类的主要功能是每隔100ms更新一下进度,最后将所有的进度值加到链表中作为耗时操作的执行结果返回,用于界面显示,是不是很简单呢~!