背景
AsyncTask是Android中异步请求的基础类。由于在Android系统是单线程模型,即只能在主线程更新UI。之所以这样设计,主要是为了避免多个线程对UI同时操作造成的混乱。另一方面,Android是一个多线程的操作系统,我们不可能把所有操作都放在主线程中操作,如网络请求,读取存储的数据,等等。否则会抛出ANR异常。所以,有必要把耗时操作放在非UI线程中执行,而仅在UI线程更新UI。这样既保证了Android系统的单线程模型,又避免了程序的ANR。
AsyncTask是Android封装好的用于实现异步请求的类。利用这个类,可以很方便的在子线程中更新UI,同时简化异步操作。
AsyncTask基础
AsyncTask是一个抽象类,它包含了三个泛型参数。一般会继承该类。
AsyncTask<Params,Progress,Result>
其中:
泛型参数一:Params。它是启动任务时的输入参数类型;
泛型参数二:Progress。它是后台任务执行时返回的进度值的类型;
泛型参数三:Result。后台任务执行完成后返回的结果类型。
AsyncTask中必须重写的方法:
doInBackground():该方法必须重写,用于执行后台的异步任务。所有耗时操作都在该方法中执行。
onPreExecute():执行耗时操作前被调用。完成一些初始化操作。该方法在主线程中执行。
onPostExecute():在doInBackground()方法执行完毕后,该方法会被调用,并将返回的值传递至该方法中。该方法在主线程中执行;
onProgressUpdate():在 doInBackground()中调用publishProgress()方法,可将当前后台执行的进度发送到该方法中。该方法在主线程中执行。
新建一个MyAsyncTask类继承自AsyncTask,并重写上述方法,打印Log,各方法的调用顺序如下:
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
public static final String TAG = "MyAsyncTask";
@Override
protected Void doInBackground(Void... params) {
Log.e(TAG, "doInBackground");
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.e(TAG, "onPreExecute");
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Log.e(TAG, "onPostExecute");
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
Log.e(TAG, "onProgressUpdate");
}
}
调用顺序:
顺序为:onPreExecute()—>doInBackground()—>onPostExecute()
若在doInBackground()中加入方法 publishProgress()方法,再次运行程序,打印的Log为:
顺序为:onPreExecute()—>doInBackground()—>onProgressUpdate()—>onPostExecute()
demo1:使用AsyncTask加载一张网络图片
在使用AsyncTask加载完成之前,会实现一个progressBar,提示用户等待。加载完毕后,隐藏progressBar。
布局代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_image_load"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.demo.lenovo.asynctasktest.ImageLoadActivity">
<ImageView
android:id="@+id/iv_image"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/pb_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone" />
</RelativeLayout>
首先将ProgressBar设为Gone,即不显示。
接着是Activity:
public class ImageLoadActivity extends AppCompatActivity {
private static final String URL = "https://img-my.csdn.net/uploads/201609/14/1473820894_3292.png";
private ImageView iv_image;
private ProgressBar pb_progress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_load);
iv_image = (ImageView) findViewById(R.id.iv_image);
pb_progress = (ProgressBar) findViewById(R.id.pb_progress);
new ImgLoadAsyncTask().execute(URL);
}
private class ImgLoadAsyncTask extends AsyncTask<String, Void, Bitmap> {
@Override
protected void onPreExecute() {
super.onPreExecute();
pb_progress.setVisibility(View.VISIBLE);
}
@Override
protected Bitmap doInBackground(String... params) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String url = params[0];
URLConnection urlConnection = null;
Bitmap bitmap = null;
InputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
urlConnection = new URL(url).openConnection();
inputStream = urlConnection.getInputStream();
bufferedInputStream = new BufferedInputStream(inputStream);
bitmap = BitmapFactory.decodeStream(bufferedInputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (bufferedInputStream != null) {
bufferedInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
iv_image.setImageBitmap(bitmap);
pb_progress.setVisibility(View.GONE);
}
}
}
在Activity中,将AsyncTask设为内部类,并将泛型参数分别设为String, Void, Bitmap。其中String表示加载的图片URL地址,Void表示不需要在加载过程中通知主线程更新进度,Bitmap表示加载完毕后返回一张Bitmap图。
在onPreExecute()方法中,首先将ProgressBar设为可见状态;在doInBackground()方法中从泛型数组String中获取url地址,通过URLConnection和InputStream网络请求基础类请求url,最终转化为Bitmap图像并返回;最后,在onPostExecute()方法中将Bitmap设置到ImageView上并将ProgressBar关闭。(在doInBackground()方法中加入一个Thread.sleep()以模拟网络请求的延迟。)
demo2:使用AsyncTask模拟进度条
在demo中,将模拟进度条的更新等待操作。
首先,布局就是一个横向的ProgressBar:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_progress_load"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.demo.lenovo.asynctasktest.ProgressLoadActivity">
<ProgressBar
android:id="@+id/pb_load_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
接着是Activity:
public class ProgressLoadActivity extends AppCompatActivity {
private ProgressBar pb_load_progress;
private ProgressLoadAsyncTask mProgressLoadAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progress_load);
pb_load_progress = (ProgressBar) findViewById(R.id.pb_load_progress);
mProgressLoadAsyncTask = new ProgressLoadAsyncTask();
mProgressLoadAsyncTask.execute();
}
private class ProgressLoadAsyncTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < 100; ++i) {
//根据AsyncTask指定的泛型,该方法可传入一个Integer变长数组作为参数
publishProgress(i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
pb_load_progress.setProgress(values[0]);
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
pb_load_progress.setVisibility(View.GONE);
}
}
}
在Activitiy中使用AsyncTask模拟进度条的更新,由于不需要传入参数,所以泛型参数一是Void,而在doInBackground中,调用publishProgress(),传入的值将传给onProgressUpdate()方法,所以泛型参数二是Integer类型。该操作也不需要返回参数,所以泛型参数三也是Void类型。
最后,在onPostExecute()中隐藏ProgressBar。
取消Task
在运行过程中,会遇到一个问题:
- 当进度条未执行完毕时,退出该Activity,并立即再次启动,发现进度条并没有从上次退出的进度继续执行,也没有从头执行。
原因:
- 由于AsyncTask的底层实现是线程池,只有当一个线程执行完毕以后,下一个线程才会开始,所以,只有当doInBackground()方法的内容全部执行完毕以后,才能得到下一次执行。
解决方法:
- 使AsyncTask的生命周期与Activity的生命周期一致。
具体解决方式:
在Activity的onPause()中判断AsyncTask的状态并将其停止:
@Override
protected void onPause() {
super.onPause();
//当AsyncTask不为空且AsyncTask的状态为正在运行
if (mProgressLoadAsyncTask != null && mProgressLoadAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
//只是将对应的AsyncTask标记为cancel状态,并没有真正取消该线程。
mProgressLoadAsyncTask.cancel(true);
}
}
需要注意的是cancel()方法只是将对应的AsyncTask标记为cancel状态,并没有真正取消该线程。下面是cancel()方法的文档:
其中第二段说:调用cancel()方法将会触发onCancelled(Object)在doInBackground()方法结束后调用(并且onCancelled(Object)是在UI线程中调用),同时您还要确保onPostExecute(Object) 不会被调用。最后还需要不断在doInBackground方法中检查isCancelled() 并及时手动停止该AsyncTask。
所以,除了在onPause中调用cancel方法外,还需要在doInBackground方法中调用isCancelled()判断状态,并将onPostExecute方法中的逻辑复制到onCancelled()方法中(如您需要在onCancelled()中直线自己的操作,如关闭进度条等,请不要调用super.onCancelled()):
在doInBackground和onProgressUpdate方法中,不断判断isCancelled()方法,若为true即终止任务:
@Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < 100; ++i) {
if (isCancelled()) {
break;
}
//根据AsyncTask指定的泛型,该方法可传入一个Integer变长数组作为参数
publishProgress(i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
if (isCancelled()) {
return;
}
pb_load_progress.setProgress(values[0]);
}
重新运行程序,当中途退出Activity,并再次进入后,可以进度条从头加载。
总结
必须在UI线程中创建AsyncTask实例;
必须在UI线程中调用AsyncTask.execute()方法;
不能手动调用AsyncTask的回调方法;
只能调用AsyncTask.execute()一次,不可重复调用。
只有doInBackground()方法运行在子线程中,其余回调方法均运行在主线程中。