程序员徐公

微信公众号:【徐公】

0%

AsyncTask 使用及封装实践

前言

IntentService使用及源码分析

HandlerThread源码分析

AsyncTask使用及封装实践

AsyncTask源码分析

这篇博客主要是讲解AsyncTask的使用及封装实践,对于新手们还是有很大的参考意义的,尤其是AsyncTask的封装实践这部分。对于老鸟们,你们可以跳过了。同时需要声明的一点是,下面下载的例子只是进行简单的下载而已,并没有支持断点续传下载。需要的话请自行到github上面找相应的库,因为这并不是本篇博客的重点。

这篇博客主要讲解以下问题:

  • AsyncTask的简单使用
  • AsyncTask的封装使用
  • AsyncTask使用注意事项

AsyncTask的使用例子

简介

AsyncTask ,异步任务。没错,就想字面上理解的那样。它允许我们在子线程执行耗时任务,在UI 线程更新操作(如更新进度条等)。简单来说,就是帮我们做好了子线程与UI 线程的通讯,我们只需要调用响应的方法实现即可。底层是用Handler消息机制实现的。

在Android开发中,我们经常需要下载各种东西,为了给用户较好的体验,我们经常需要显示下载进度。今天我们用以这个为例子,来教大家怎样使用AsyncTak。当然,github上面有很多开源库,实现断点下载,文件重命名等。不过这些不是本篇博客的重点。

效果图

AsyncTask的主要几个方法

  • Void onPreExecute()

在task 任务开始执行的时候调用,在doInBackground(Params… params)方法之前调用,在主线程中执行

  • Result doInBackground(Params… params)

主要用来执行耗时操作,在子线程中执行,Params为我们参数的类型。而Result这个泛型,是我们返回的类型(可以是Integer,Long,String等等类型,只要不是八种基本类型就OK),同时 Result 的类型将作为 onPostExecute(Result result)的参数。

  • Void onProgressUpdate(Progress… values)

Runs on the UI thread after publishProgress(Progress…) is invoked. 当我们调用 publishProgress()方法的时候,会调用 onProgressUpdate()这个方法

  • Void onPostExecute(Result result)
    在doInBackground()方法执行完毕之后,会调用这个方法,是在主线程中执行的。但如果我们手动调用了cancelled()方法,那么这个方法将不会被调用。

  • void onCancelled()

在Task 任务取消的时候会调用

  • execute(Params… params)

Executes the task with the specified parameters.当我们调用这个方法的时候,会执行任务

  • executeOnExecutor(Executor exec, Params… params)

在指定的线程池里面执行Task

需要注意的是,Params,Progress,Result 并不是一种特定的类型,它其实是泛型,它支持除了八种基本类型之外的类型,跟普通的泛型一样。

AsyncTask使用的几个步骤

这里我们以下载一个apk为例讲解

  1. 写一个类继承AsyncTask,并传入Params,Progress,Result 。三个参数的类型。

比如我们传入的 Params,Progress,Result 的参数的类型分别为 Void, FileInfo, FileInfo,那我们可以这样写。

1
2
3
private class MyDownloadTask extends AsyncTask<Void, FileInfo, FileInfo>{

}

那Void, FileInfo, FileInfo,这几个参数的类型在哪里体现出来呢?

请看下面注释

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
private class MyDownloadTask extends AsyncTask<Void, FileInfo, FileInfo> {

---

// 方法参数的类型为Void,跟我们传入的Void一致,返回类型为 FileInfo ,跟我们传入Result的类型FileInfo一致
@Override
protected FileInfo doInBackground(Void... params) {

}

// 方法参数类型为FileInfo,跟我们传入Progress的类型FileInfo一致
@Override
protected void onProgressUpdate(FileInfo... values) {

}


// 方法参数FileInfo,跟我们传入Result的类型FileInfo一致
@Override
protected void onPostExecute(FileInfo fileInfo) {


}


}
  1. 如果我们更新进度的话,需要重写 onProgressUpdate()方法,并在doInBackground()方法里面调用publishProgress()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected FileInfo doInBackground(Void... params) {

publishProgress(fileInfo);


}

@Override
protected void onProgressUpdate(FileInfo... values) {
super.onProgressUpdate(values);
refreshProgress(values[0]);
}


  1. 当我们调用execute(Params… params) 或者 executeOnExecutor(Executor exec, Params… params) 方法的时候,Task将被防盗相应的 Executor 执行。
1
2
MyDownloadTask myDownloadTask = new MyDownloadTask(mDownloadUrl, mDstPath);
myDownloadTask.execute();

完整的Task代码如下

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
private class MyDownloadTask extends AsyncTask<Void, FileInfo, FileInfo> {

String mDownLoadUrl;
String mDstPath;

public MyDownloadTask(String downloadUrl, String dstPath) {
this.mDownLoadUrl = downloadUrl;
this.mDstPath = dstPath;
}

@Override
protected void onPreExecute() {
super.onPreExecute();
start();
}

@Override
protected FileInfo doInBackground(Void... params) {
//url字符串,检查网址是否已http:// 开头
mDownLoadUrl = (mDownLoadUrl.startsWith("http://")) ? mDownLoadUrl : "http://" +
mDownLoadUrl;
Log.d(TAG, "doInBackground: mDownLoadUrl=" + mDownLoadUrl);
Log.d(TAG, "doInBackground: mDstPath=" + mDstPath);
URL url = null;
FileInfo fileInfo = null;
int contentLength = -1;
int downloadLength = 0;
OutputStream output = null;
InputStream istream = null;
try {
url = new URL(mDownLoadUrl);
//打开到url的连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
contentLength = connection.getContentLength();
Log.i(TAG, "doInBackground: contentLength=" + contentLength);
//O部分,大体来说就是先检查文件夹是否存在,不存在则创建
istream = connection.getInputStream();
String filename = mDownLoadUrl.substring(mDownLoadUrl.lastIndexOf("/") + 1);

File dir = new File(mDstPath);
if (!dir.exists()) {
dir.mkdir();
}
File file = new File(mDstPath + filename);
// 如果存在同名文件,重命名
if (file.exists()) {
file = FileUtils.rename(file.getPath());
}


output = new FileOutputStream(file);
byte[] buffer = new byte[1024 * 4];
int count = 0;
int len = -1;
while ((len = istream.read(buffer)) != -1) {
output.write(buffer, 0, len);
downloadLength += len;

if (count == 10) {
fileInfo = new FileInfo(contentLength, downloadLength, file, file.getPath
(), file.getName());
publishProgress(fileInfo);
count = 0;
}
count++;

}
// 有可能count还没有走到10
fileInfo = new FileInfo(contentLength, downloadLength, file, file.getPath(), file
.getName());
publishProgress(fileInfo);
output.flush();
output.close();
istream.close();

} catch (Exception e) {
e.printStackTrace();
try {
IOUtils.close(output);
IOUtils.close(istream);
} catch (IOException e1) {
e1.printStackTrace();
}

} finally {
try {
IOUtils.close(output);
IOUtils.close(istream);
} catch (IOException e1) {
e1.printStackTrace();
}
}
return fileInfo;
}

@Override
protected void onProgressUpdate(FileInfo... values) {
super.onProgressUpdate(values);
refreshProgress(values[0]);
}

@Override
protected void onPostExecute(FileInfo fileInfo) {
super.onPostExecute(fileInfo);
downloadfinish(fileInfo);

}

@Override
protected void onCancelled() {
super.onCancelled();
}
}

private void start() {
mTvDownloadText.setText("开始下载");
mProgressBar.setMax(100);
mProgressBar.setProgress(0);
}

private void downloadfinish(FileInfo fileInfo) {
Log.i(TAG, "onPostExecute: 下载完成=" + fileInfo.mPath);
Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
}

private void refreshProgress(FileInfo value) {
FileInfo fileInfo = value;
if (fileInfo != null) {
mProgressBar.setMax((int) fileInfo.mLength);
mProgressBar.setProgress((int) fileInfo.mDownloadLength);
mDownText = fileInfo.mFile.getName() + "下载了" + fileInfo.mDownloadLength + "总长度是" +
fileInfo.mLength;
mTvDownloadText.setText(mDownText);
}
}



AsyncTask的封装使用

前面我们讲完了AsyncTask的基本使用,不知道你有没有发现,其实代码耦合性是挺高的,

  • 我们直接在 onProgressUpdata(),onPostExecute()方法里面更新我们的界面,即我们的AsyncTask访问了我们Activity里面的控件,那如果我们修改了Activity的控件,我们岂不是又要去阅读AsyncTask的代码,去做相应的修改。
  • 下一次我们如果要下载别的东西,按照我们前面的代码,我们又要重新复制一份,这样无疑是做了很多重复的工作。

说到这样,我相信大多数人的第一感觉就是把AsyncTask提取为外部类,封装起来。是的,确实,我们就是要把AsyncTask提取为外部类。那提取为歪不累之后呢?我们要访问Activity里面的空间,要怎样访问呢?

  1. 在Activity里面定义静态方法
  2. 把需要访问的View对象通过构造函数传递进来
  3. 采用接口回调机制

前面说到的三种方法,是可以做到AsyncTask与外界进行通讯的。但第一第二中方法明显不行。原因如下:

  • 第一种方法定义静态方法,那View对象也必须定义为static变量,这static变量的级别比较高,不易被垃圾回收机制回收,易发生没存泄露。
  • 第二种方法,把需要访问的View对象通过构造函数传递进来。如果需要访问的对象少的话,勉强可以接受,如果多的话,那岂不是要定义很多成员变量。不过最致命的还算是代码耦合性太高了。还不如AsyncTask直接作为内部类。

好了,说了这么多,下面我们一起来看怎样使用接口回调机制来进行解耦。

AsyncTask 使用接口回调机制来进行解耦

  1. 使用接口回调机制,首先我们必须有一个接口
1
2
3
4
5
6
7
8
public interface DownloadListener {

void onStart();
void onProgress(FileInfo fileInfo);
void onFinish(FileInfo FileInfo);
void onPaused(FileInfo fileInfo);
void onCancled();
}
  1. 将DownLoadTask提取为一个外部类,并将需要传递的参数传递进来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DownloadTask extends AsyncTask<Void,FileInfo,FileInfo> {

private String mDownloadUrl;
private final String mDstPath;
private final String mFileName;
private final DownloadListener mDownloadListener;

public DownloadTask(String downloadUrl, String dstPath, String fileName, DownloadListener downloadListener){
mDownloadUrl = downloadUrl;
mDstPath = dstPath;
mFileName = fileName;
mDownloadListener = downloadListener;
}
}
  1. 在相应的地方调用我们接口的方法
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
public class DownloadTask extends AsyncTask<Void,FileInfo,FileInfo> {

----

@Override
protected void onPreExecute() {
super.onPreExecute();
mDownloadListener.onStart();
}

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

----
int len = -1;
while ((len = istream.read(buffer)) != -1) {
output.write(buffer, 0, len);
downloadLength += len;

if (count == 10) {
fileInfo = new FileInfo(contentLength, downloadLength, file, file.getPath
(), file.getName());
publishProgress(fileInfo);
count = 0;
}
count++;

}
// 有可能count还没有走到10
fileInfo = new FileInfo(contentLength, downloadLength, file, file.getPath(), file
.getName());
publishProgress(fileInfo);
output.flush();
output.close();
istream.close();



return fileInfo;
}

@Override
protected void onProgressUpdate(FileInfo... values) {
super.onProgressUpdate(values);
mDownloadListener.onProgress(values[0]);
}

@Override
protected void onPostExecute(FileInfo fileInfo) {
super.onPostExecute(fileInfo);
mDownloadListener.onFinish(fileInfo);
}

@Override
protected void onCancelled() {
super.onCancelled();
mDownloadListener.onCancled();
}
}

使用

以后我们要下载东西,只需要调用下面的方法即可。同时,如果产品再更改需求,比如,从显示一个进度条ProgressDialog对话框,改成显示一个ProgressBar,我们只需要在
onProgress()里面做相应的修改就好了,在也不用去阅读DownloadTask里面的代码呢?减少了代码的耦合性,是不是瞬间感觉世界很美好呢?

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
mDownloadTask = new DownloadTask(mDownloadUrl, mDstPath, null, new
DownloadListener() {
@Override
public void onStart() {
start();
}

@Override
public void onProgress(FileInfo fileInfo) {
refreshProgress(fileInfo);
}

@Override
public void onFinish(FileInfo fileInfo) {
downloadfinish(fileInfo);
}

@Override
public void onPaused(FileInfo fileInfo) {

}

@Override
public void onCancled() {

}
});
mDownloadTask.execute();

AsyncTask使用的注意事项

  • The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.
  • The task instance must be created on the UI thread.(AsyncTask必须在UI 线程里面初始化
  • execute(Params…) must be invoked on the UI thread.
  • Do not call onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…) manually.(不要手动地调用 onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…) 这些方法)
  • The task can be executed only once (an exception will be thrown if a second execution is attempted.) (Task任务只能被执行一次,否则会抛出异常)

相关知识点推荐:

IntentService使用及源码分析

HandlerThread源码分析

AsyncTask使用及封装实践

AsyncTask源码分析

Demo下载地址