目录
前言
一、AsyncTask基础
二、使用步骤
三、注意事项
1、 关于 生命周期
2、 关于 内存泄漏
3、 线程任务执行结果 丢失
四、案例:AsyncTask下载文件
五、多实例并行
Async | Task
async [æˈsɪŋk] abbr. 异步,非同步 | 英 [tɑːsk] 美 [tæsk]n. (困难的)任务,工作;
AsyncTask,称为异步任务,属于原生类。它对Thread和Handler进行了封装,方便在子线程中执行耗时任务,然后将结果发送给主线程进行UI更新等操作。使用AsyncTask就可以不用关注Thread和Handler,因为AsyncTask内部会对其进行管理,我们就只需要关注我们的业务逻辑即可。
建议先看一遍——Android Handler机制使用,源码分析
源码类如下:
//抽象类
public abstract class AsyncTask {
//线程池类型
public static final Executor SERIAL_EXECUTOR = null;
public static final Executor THREAD_POOL_EXECUTOR = null;
public AsyncTask() {}
protected abstract Result doInBackground(Params... var1);
protected void onPreExecute() {}
protected void onPostExecute(Result result) {}
protected void onProgressUpdate(Progress... values) {}
protected final void publishProgress(Progress... values) { }
public final boolean isCancelled() {}
public final boolean cancel(boolean mayInterruptIfRunning) {}
protected void onCancelled() { }
...//省略
}
AsyncTask类中参数为3种泛型类型,控制AsyncTask子类执行线程任务时各个阶段的返回类型。如下所示:
- Params :开始异步任务执行时传入的参数类型,对应excute()中传递的参数
- Progress:异步任务执行过程中,返回下载进度值的类型
- Result :异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
其中,Params用于AsyncTask调用execute()方法时传入后,依次执行下面其他方法。
创建AsyncTask子类,继承AsyncTask类,为3个泛型参数指定类型;
若不使用,可用java.lang.Void类型代替
private class MyTask extends AsyncTask {
//线程池对象
public static final Executor SERIAL_EXECUTOR = null;
public static final Executor THREAD_POOL_EXECUTOR = null;
//执行线程任务前的操作
@Override
protected void onPreExecute() {}
//接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
@Override
protected String doInBackground(String... params) {
...// 自定义的线程任务
// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
publishProgress(count);
}
// 在主线程 显示线程任务执行的进度
@Override
protected void onProgressUpdate(Integer... progresses) {}
// 接收线程任务执行结果、将执行结果显示到UI组件
@Override
protected void onPostExecute(String result) { }
// 将异步任务设置为:取消状态
@Override
protected void onCancelled() {}
}
创建AsyncTask子类的实例对象,AsyncTask子类的实例必须在UI线程中创建
MyTask mTask = new MyTask();
手动调用execute(Params... params) 从而执行异步线程任务,同样也必须在UI线程中调用。另外,同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常。
mTask.execute();
在使用AsyncTask
时有一些问题需要注意的:
AsyncTask
不与任何组件绑定生命周期Activity
或 Fragment
中使用 AsyncTask
时,最好在Activity
或 Fragment
的onDestory()
调用 cancel(boolean)
;AsyncTask
被声明为Activity
的非静态内部类,当Activity
需销毁时,会因AsyncTask
保留对Activity
的引用 而导致Activity
无法被回收,最终引起内存泄露AsyncTask
应被声明为Activity
的静态内部类Activity
重新创建时(屏幕旋转 / Activity
被意外销毁时后恢复),之前运行的AsyncTask
(非静态的内部类)持有的之前Activity
引用已无效,故复写的onPostExecute()
将不生效,即无法更新UI操作Activity
恢复时的对应方法 重启 任务线程
Layout布局文件:
界面上有一个“开始下载”的按钮,点击该按钮即可下载。对应的Java代码:
public class MainActivity extends Activity implements Button.OnClickListener {
TextView textView = null;
Button btnDownload = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.textView);
btnDownload = (Button)findViewById(R.id.btnDownload);
Log.i("iSpring", "MainActivity -> onCreate, Thread name: " + Thread.currentThread().getName());
}
@Override
public void onClick(View v) {
//要下载的文件地址
String[] urls = {
"http://blog.csdn.net/iispring/article/details/47115879",
"http://blog.csdn.net/iispring/article/details/47180325",
"http://blog.csdn.net/iispring/article/details/47300819",
"http://blog.csdn.net/iispring/article/details/47320407",
"http://blog.csdn.net/iispring/article/details/47622705"
};
DownloadTask downloadTask = new DownloadTask();
downloadTask.execute(urls);
}
//在此例中,Params泛型是String类型,Progress泛型是Object类型,Result泛型是Long类型
private class DownloadTask extends AsyncTask {
@Override
protected void onPreExecute() {
Log.i("iSpring", "DownloadTask -> onPreExecute, Thread name: " + Thread.currentThread().getName());
super.onPreExecute();
btnDownload.setEnabled(false);
textView.setText("开始下载...");
}
@Override
protected Long doInBackground(String... params) {
Log.i("iSpring", "DownloadTask -> doInBackground, Thread name: " + Thread.currentThread().getName());
//totalByte表示所有下载的文件的总字节数
long totalByte = 0;
//params是一个String数组
for(String url: params){
//遍历Url数组,依次下载对应的文件
Object[] result = downloadSingleFile(url);
int byteCount = (int)result[0];
totalByte += byteCount;
//在下载完一个文件之后,我们就把阶段性的处理结果发布出去
publishProgress(result);
//如果AsyncTask被调用了cancel()方法,那么任务取消,跳出for循环
if(isCancelled()){
break;
}
}
//将总共下载的字节数作为结果返回
return totalByte;
}
//下载文件后返回一个Object数组:下载文件的字节数以及下载的博客的名字
private Object[] downloadSingleFile(String str){
Object[] result = new Object[2];
int byteCount = 0;
String blogName = "";
HttpURLConnection conn = null;
try{
URL url = new URL(str);
conn = (HttpURLConnection)url.openConnection();
InputStream is = conn.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int length = -1;
while ((length = is.read(buf)) != -1) {
baos.write(buf, 0, length);
byteCount += length;
}
String respone = new String(baos.toByteArray(), "utf-8");
int startIndex = respone.indexOf("");
if(startIndex > 0){
startIndex += 7;
int endIndex = respone.indexOf(" ");
if(endIndex > startIndex){
//解析出博客中的标题
blogName = respone.substring(startIndex, endIndex);
}
}
}catch(MalformedURLException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}finally {
if(conn != null){
conn.disconnect();
}
}
result[0] = byteCount;
result[1] = blogName;
return result;
}
@Override
protected void onProgressUpdate(Object... values) {
Log.i("iSpring", "DownloadTask -> onProgressUpdate, Thread name: " + Thread.currentThread().getName());
super.onProgressUpdate(values);
int byteCount = (int)values[0];
String blogName = (String)values[1];
String text = textView.getText().toString();
text += "\n博客《" + blogName + "》下载完成,共" + byteCount + "字节";
textView.setText(text);
}
@Override
protected void onPostExecute(Long aLong) {
Log.i("iSpring", "DownloadTask -> onPostExecute, Thread name: " + Thread.currentThread().getName());
super.onPostExecute(aLong);
String text = textView.getText().toString();
text += "\n全部下载完成,总共下载了" + aLong + "个字节";
textView.setText(text);
btnDownload.setEnabled(true);
}
@Override
protected void onCancelled() {
Log.i("iSpring", "DownloadTask -> onCancelled, Thread name: " + Thread.currentThread().getName());
super.onCancelled();
textView.setText("取消下载");
btnDownload.setEnabled(true);
}
}
}
控制台输出如下所示:
我们上面提到,对于某个AsyncTask实例,只能执行一次execute方法,如果我们想并行地执行多个任务,难道要实例化多个AsyncTask实例对象,然后分别调用各个实例的execute方法吗?为了探究效果,我们将代码更改如下所示:
public void onClick(View v) {
//要下载的文件地址
String[] urls = {
"http://blog.csdn.net/iispring/article/details/47115879",
"http://blog.csdn.net/iispring/article/details/47180325",
"http://blog.csdn.net/iispring/article/details/47300819",
"http://blog.csdn.net/iispring/article/details/47320407",
"http://blog.csdn.net/iispring/article/details/47622705"
};
DownloadTask downloadTask1 = new DownloadTask();
downloadTask1.execute(urls);//第一个实例执行
DownloadTask downloadTask2 = new DownloadTask();
downloadTask2.execute(urls);//第二个实例执行
}
通过观察控制台的输出结果,可以发现downloadTask1的doInBackground方法是运行在线程“AsyncTask #1”中的。downloadTask2的doInBackground方法是运行在线程”AsyncTask #2”中的。
我们对比上面的GIF图发现,在downloadTask1按照顺序下载完五篇文章之后,downloadTask2才开始按照顺序下载五篇文章。分析日志发现downloadTask1的doInBackground方法执行后,下载了五个文件,并触发五次onProgressUpdate,而之后才去执行downloadTask2的doInBackground方法。
综上所述,AsyncTask创建了多个实例并同时执行各自execute方法,这些实例的execute方法不是并行的,而是串行的同步的。即在第一个实例的doInBackground完成任务后,第二个实例的doInBackgroud方法才会开始执行。
原因是,AsyncTask为downloadTask1开辟了名为”AsyncTask #1”的工作线程,在其完成了任务之后就销毁了,然后又为downloadTask2开辟了名为”AsyncTask #2”的工作线程,所以两个线程可能并不是同时存在。
从Android 3.0开始AsyncTask增加了executeOnExecutor方法,用该方法可以并行处理任务。方法如下:
public final AsyncTask
executeOnExecutor(Executor exec, Params... params)
第一个参数是一个Executor线程池对象,为了让AsyncTask并行处理任务。通常情况下我们此处传入
AsyncTask.THREAD_POOL_EXECUTOR
即可。第二个参数params表示的是要执行的任务的参数。
示例代码如下:
public void onClick(View v) {
if(Build.VERSION.SDK_INT >= 11){
String[] urls = {
"http://blog.csdn.net/iispring/article/details/47115879",
"http://blog.csdn.net/iispring/article/details/47180325",
"http://blog.csdn.net/iispring/article/details/47300819",
"http://blog.csdn.net/iispring/article/details/47320407",
"http://blog.csdn.net/iispring/article/details/47622705"
};
DownloadTask downloadTask1 = new DownloadTask();
downloadTask1.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, urls);//看这里!
DownloadTask downloadTask2 = new DownloadTask();
downloadTask2.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, urls);//看这里!
}
}
小结:
通过控制台的输出结果我们可以看到,在downloadTask1执行了doInBackground方法后,downloadTask2也立即执行了doInBackground方法。并且通过程序运行完的UI界面可以看到在一个DownloadTask实例下载了一篇文章之后,另一个DownloadTask实例也立即下载了一篇文章,两个DownloadTask实例交叉按顺序下载文件,可以看出这两个AsyncTask的实例是并行执行的。