Android笔记之AsyncTask基础

AsyncTask的基本构成

AsyncTask概述

为什么要异步任务

  1. Android中,只有主线程可以对UI进行操作,而其他线程是不可以直接操作UI的。这用做的好处是保证了UI的稳定性和准确性。避免了多线程同时对UI进行操作,而造成对UI的一个混乱。
  2. 安卓是个多线程的系统,我们不可能把所有的事情都放在主线程中。比如一些网络操作、读取文件等一些耗时的操作。如果把这些操作都放在了主线程中,就会造成阻塞,从用户的角度,就会感觉这个App很卡或者未响应。所以,应该把耗时的操作放在非主线程中。

    AsyncTask为何而生

    那么,主线程和子线程如何进行通信呢?
    要使用异步任务,可以使用线程和线程池,在这里涉及到了一个线程的同步和线程的管理。同时,当线程结束的时候,我们还需要通过handle去通知主线程更新UI。而AsyncTask对这一切都封装了起来,让我们可以更方便地在子线程中更新UI。总结起来AsyncTask的功能就是:
  • 子线程中更新UI
  • 封装、简化异步操作

AsyncTask基本结构介绍

构建AsyncTask子类的参数

AsyncTask是一个抽象类,通常用于被继承,继承AsyncTask需要指定如下三个泛型参数:

params:启动任务时输入参数的类型。
progress:后台任务执行中返回进度值的类型。
Result:后台执行任务完成后返回结果的类型。
注:如果不需要param或者progress,设置为void即可。

构建AsyncTask子类的回调方法

doInBackground: 必须重写,异步执行后台线程将要完成的任务;注意:这个方法中是不可以对UI操作的,如果需要更新UI元素,可以调用publicProgress()方法。
onPreExecute:执行后台耗时操作前被调用,通常用户完成一些初始化操作。
onPostExecute:当doInBackground()完成后,系统会自动调用onPostExecute()方法,并将doInBackground方法返回的值传给该方法;再对返回值进行处理。改方法执行在主线程中,所以可以在该方法中对UI进行操作。
onProgressUpdate:在doInBackground()方法中调用publicProgress()方法,。更新任务的执行进度后,就会触发该方法。通过这个方法,我们就可以清楚的知道任务完成的进度。publicProgress()用于传入进度值,onProgressUpdate则获取这个进度,更新进度条。

这些方法的执行顺序:
onPreExecute—>doInBackground—>onProgressUpdate(当在doInBackground()中调用publicProgress()时才会执行)—>onPostExecute

AsyncTask的使用示例一

下载图片的时间取决于网速,网速慢的情况下是一个耗时操作,应在子线程中下载一张图片,然后同步到主线程中的UI中。
异步处理—>下载图片
UI线程—>设置图像

效果:点击开始界面的按钮,进入下一个活动,首先出现下载图片的等待ProgressBar,然后显示图片。

涉及的文件有:
MainActivity.java:默认进入的活动,有一个按钮,点击后进入ImageActivity。
ImageActivity.java:显示图片的活动
activity_main.xml:默认活动的布局
activity_image.xml:图片活动的布局

创建UI

首先,找到一张网络图片,并记住链接;然后创建一个新的活动,并在对应的xml中布局,即设置一个Image控件和一个ProgressBar控件。ProgressBar设置默认visibility为隐藏gone。最后在活动中setContentView这个布局,创建必要控件变量以及图片链接URL。

main活动的xml布局代码:

1
2
3
4
5
6
<Button
android:id="@+id/btn_next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="next"
android:textSize="25sp"/>

对应的main活动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainActivity extends AppCompatActivity
{

private Button button;
@Override
protected void onCreate(Bundle savedInstanceState)
{

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.btn_next);
button.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{

startActivity(new Intent(MainActivity.this, ImageActivity.class));
}
});
}
}

image活动的xml布局代码:

1
2
3
4
5
6
7
8
9
10
<ImageView
android:id="@+id/img_pic1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

<ProgressBar
android:id="@+id/progressBar1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>

对应的Image活动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ImageActivity extends AppCompatActivity
{

private ImageView imageView;
private ProgressBar progressBar;
private static String URL = "http://ico.ooopic.com/ajax/iconpng/?id=132347.png";
@Override
protected void onCreate(Bundle savedInstanceState)
{

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image);
imageView = (ImageView)findViewById(R.id.img_pic1);
progressBar = (ProgressBar)findViewById(R.id.progressBar1);

}
}

创建AsyncTask类

在ImageActivity里创建内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class MyAsyncTask extends AsyncTask<String, Void, Bitmap>//Bitmap位图类
{

@Override
protected void onPreExecute()
{

super.onPreExecute();
progressBar.setVisibility(View.VISIBLE);//显示进度条
}

@Override
protected Bitmap doInBackground(String... params)//传进来的是一个可变长的数组
{

//获取传递进来的参数
String url = params[0];//取出对应的URL
Bitmap bitmap = null;//所要获取的一个bitmap
URLConnection connection;//定义网络连接对象
InputStream is;//用于获取数据的输入流
//以上都是获取网络所必须的参数。
try
{
connection = new URL(url).openConnection();//获取网络连接对象
is = connection.getInputStream();//获取输入流
BufferedInputStream bis = new BufferedInputStream(is);
Thread.sleep(3000);//睡个3秒钟,不让图片太快显示,不然看不到效果。
bitmap = BitmapFactory.decodeStream(bis);//将输入流解析成bitmap
//关闭输入流
is.close();
bis.close();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return bitmap;
}

@Override
protected void onPostExecute(Bitmap bitmap)//操作UI,设置图像
//doInBackground返回一个bitmap的参数传入onPostExecute中,从而可以对刚刚获取到的bitmap图设置为Image的src
{

super.onPostExecute(bitmap);
progressBar.setVisibility(View.GONE);
imageView.setImageBitmap(bitmap);
}
}

总结一下流程就是:

  1. onPreExecute—>加载进度条
  2. doInBackground—>下载网络数据(耗时操作)
  3. onPostExecute—>显示图片

与UI线程通信

onPostExecute方法就是将子线程中返回的结果在主线程中处理。

修改补充ImageActivity的onCreate方法:

1
2
3
4
5
6
7
8
9
10
@Override
protected void onCreate(Bundle savedInstanceState)
{

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image);
imageView = (ImageView)findViewById(R.id.img_pic1);
progressBar = (ProgressBar)findViewById(R.id.progressBar1);
new MyAsyncTask().execute(URL);//开启异步线程,此处的URL传入到doInBackground中

}

效果如下:

AsyncTask的使用示例二

模拟进度条

效果:点击开始界面的按钮,进入下一个活动,首先出现ProgressBar读取进度。

代码实现

注释里写的很清楚了,这里就直接给代码了。
activity_main.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">


<Button
android:id="@+id/btn_next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="next"
android:textSize="25sp"/>

</RelativeLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.demoniaccube.chobits.asynctask_test;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;

public class MainActivity extends AppCompatActivity
{

private Button button;
@Override
protected void onCreate(Bundle savedInstanceState)
{

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.btn_next);
button.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{

startActivity(new Intent(MainActivity.this, ProgressBarActivity.class));
}
});
}
}

activity_progress_bar.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.demoniaccube.chobits.asynctask_test.ProgressBarActivity">


<ProgressBar
android:id="@+id/progressBar2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
style="?android:attr/progressBarStyleHorizontal"/>


</RelativeLayout>

ProgressBarActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.demoniaccube.chobits.asynctask_test;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ProgressBar;

public class ProgressBarActivity extends AppCompatActivity
{

private ProgressBar progressBar;
private MyAsyncTask myAsyncTask;

@Override
protected void onCreate(Bundle savedInstanceState)
{

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progress_bar);
progressBar = (ProgressBar)findViewById(R.id.progressBar2);
myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();//启动异步任务,这个时候就不需要传入参数了。因为没有数据需要后台处理,只是更新进度条而已。

}

@Override
protected void onPause()
{

super.onPause();
//在活动暂停的时候,对myAsyncTask进行一个判断,当myAsyncTask不为空且是正在运行的状态的时候
if (myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING)
{
myAsyncTask.cancel(true);
//取消这个myAsyncTask。true表示cancel掉这个myAsyncTask后,这个线程是否继续完成它的操作。通常为true
//这个方法并没有结束掉myAsyncTask,只是给这个AsyncTask发送了一个cancel的请求,将这个AsyncTask标记为一个cancel的状态。
//在Java中没法粗暴的去停止一个线程,必须要等到一个线程结束后再去执行其他操作;但是通过AsyncTas的cancel方法将一个AsyncTask标记为cancel
// 状态,同时在具体的异步线程中去检测这个状态值的改变,一旦AsyncTask的状态改为cancel,就跳出循环,从而结束掉整个线程的逻辑
}
}

class MyAsyncTask extends AsyncTask<Void, Integer, Void>
//这次并不需要传入数据,只需要显示进度条,所以第一个参数为Void,第二个参数要返回进度条的值,所以为IntegerIntegerint的封装类。没有传入数据处理,也没必要返回结果,所以第三个参数为Void
{

@Override
protected Void doInBackground(Void... params)
{

for (int i = 0; i < 100; i++)
{
if (isCancelled())//在每次更新前,判断Task是否被标记取消
{
break;//如果被标记取消了,就跳出循环,从而间接的结束了进程
}
publishProgress(i);//publishProgress的参数类型为Integer,即AsyncTask的第二个参数类型
try// Thread.sleep(300)异常的捕获
{
Thread.sleep(300);//300毫秒
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
return null;
}

@Override//通过publishProgress将i传递出去后,需要一个方法来承接这个值,这个方法就是onProgressUpdate
protected void onProgressUpdate(Integer... values)
//这个参数类型和AsyncTask传入的第二个参数类型一致,这个时候,通过publishProgress传入的值就作用到了这个values数组上
{

super.onProgressUpdate(values);
progressBar.setProgress(values[0]);//进度更新
}
}


}

优化

这里出现了一个bug,当从读取进度条界面退出,再进入时,会出现等待进度条的情况。

这是因为,AsyncTask底层是通过线程池作用的。当一个线程没有执行完毕的时候,后面的线程是没法执行的。由于在AsyncTask里我们用了一个for循环,所以它必须等到for循环全部执行完后才会去执行下一个Task,这就导致了重复进入ProgressBarActivity时,等待进度条的一个bug。

那么如何解决呢?
可以让这个AsyncTask周期和Activity周期保持一致。
在ProgressBarActivity暂停的时候,对myAsyncTask进行一个判断,当myAsyncTask不为空且是正在运行的状态的时候,给Task一个“取消”标记;然后在doInBackground的循环语句里,每次更新前,判断Task是否被标记取消。如果被标记取消了,就跳出循环,从而间接的结束了进程。

AsyncTask注意事项

  1. 必须在UI线程(即主线程)中创建AsyncTask的实例;
  2. 必须在UI线程中调用AsyncTask的execute()方法;
  3. 重写的四个方法是系统自动调用的,不应手动调用;
  4. 每个AsyncTask只能执行一次,多次调用将会引发异常;
  5. 在AsyncTask类中,只能doInBackground方法是在其他线程中执行的,其他三个方法是执行在主线程中的。所以这也是不能再doInBackground方法里操作UI控件的原因。