一. 前言

关于网络编程更加详细的内容可以参考:Java 网络

二. AsyncTask

1. AsyncTask是什么?

AsyncTask是Android封装的一个轻量级的异步类,可以在线程池中执行异步任务,并可以将执行进度和结果传递给UI线程。
AsyncTask的内部封装了两个线程池(SerialExecutorTHREAD_POOL_EXECUTOR)和一个Handler
其中SerialExecutor线程池用于任务的排队,让需要执行的多个耗时任务,按顺序排列,THREAD_POOL_EXECUTOR线程池才真正地执行任务,Handler用于从工作线程切换到主线程。

作用:

  • 实现多线程:在工作线程中执行任务,如 耗时任务。
  • 异步通信、消息传递:实现工作线程 & 主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作

2. AsyncTask的泛型参数

1
2
3
4
5
public abstract class AsyncTask<Params, Progress, Result>{}
#Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
#Progress:异步任务执行过程中,返回下载进度值的类型;
#Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
#如果AsyncTask确定不需要传递具体参数,那么这三个泛型参数可以用Void来代替。

3. AsyncTask的核心方法

1
2
3
@MainThread
protected void onPreExecute() {}
#此方法在doInBackground方法之前执行,在主线程中,用于进行界面初始化操作
1
2
3
@WorkerThread
protected abstract Result doInBackground(Params... params)
#此方法在WorkerThread执行,用来处理耗时任务,此方法内不能更新UI
1
2
3
4
5
6
@MainThread
protected void onProgressUpdate(Progress... values)
#当在后台任务中调用了publishProgress(Progress…)方法后,这个方法就很快会被调用,
#方法中携带的参数就是在后台任务中传递过来的。
#在这个方法中可以对UI进行操作,在主线程中进行
#利用参数中的数值就可以对界面元素进行相应的更新。
1
2
3
4
@MainThread
protected void onPostExecute(Result result)
#当doInBackground(Params…)执行完毕并通过return语句进行返回时,这个方法就很快会被调用。
#返回的数据来进行一些UI操作,在主线程中进行

上面几个方法的调用顺序:
onPreExecute() –> doInBackground() –> publishProgress() –> onProgressUpdate() –> onPostExecute() (md info supported)

1
2
3
4
5
6
7
8
@MainThread
protected void onCancelled(Result result) {
onCancelled();
}
#在主线程中执行,当异步任务取消时,onCancelled()会被调用,
#这个时候onPostExecute()则不会被调用

AsyncTask中的cancel()方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。就好比想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。

4. AsyncTask的使用

创建一个类TestTask继承AsyncTask,重写上面的几个方法,使用new TestTask().execute(参数)调用。

注意事项:

  • 异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。execute(Params… params)方法必须在UI线程中调用。

  • 一个任务实例只能执行一次,如果执行第二次将会抛出异常。

  • 内存泄漏,静态内部类持有外部类的引用。如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

  • 生命周期:activity distory 进行回收cancel。

三. 请求网页的小例子

源码地址

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
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
public class MainActivity extends AppCompatActivity {


private EditText request_text;
private Button request_btn;
private TextView show_text;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

request_text = findViewById(R.id.request_text);
request_btn = findViewById(R.id.request_btn);
show_text = findViewById(R.id.show_text);

request_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//异步执行 `
new RequestNetworkDataTask().execute(request_text.getText().toString());
}
});
}

/**
* 获取URL
* @return 返回一个URL
*/
private URL getURL(String urlString){
try {
return new URL(urlString);
} catch (MalformedURLException e) {
e.printStackTrace();

Toast.makeText(this, "URL是非法的", Toast.LENGTH_SHORT).show();
}

return null;
}

/**
* 请求数据
*/
private String requestData(String urlString){
//获取URL
URL url = getURL(urlString);

//判断URL
if (url == null){
return null;
}

//使用URL
try {
//打开连接
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
//设置响应时间
connection.setConnectTimeout(30000);
//设置请求方式
connection.setRequestMethod("GET");

//开始请求
connection.connect();

//响应代码
int responseCode = connection.getResponseCode();
//响应消息
String responseMessage = connection.getResponseMessage();

//响应成功
if (responseCode == HttpURLConnection.HTTP_OK){
//响应流
InputStream inputStream = connection.getInputStream();

//转化为字符串
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
char[] buffer = new char[1024];
reader.read(buffer);
String content = new String(buffer);
return content;
}

} catch (IOException e) {
e.printStackTrace();

Toast.makeText(this, "读写错误", Toast.LENGTH_SHORT).show();
}

return null;
}

/**
* 异步类
*/
class RequestNetworkDataTask extends AsyncTask<String,Integer,String>{

//在后台work之前
@Override
protected void onPreExecute() {
super.onPreExecute();
//主线程
//UI Loading
}


//在后台work
@Override
protected String doInBackground(String... params) {
String result = requestData(params[0]);

return result;
}

//在后台work完毕了
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
//执行完之后在主线程更新

show_text.setText(result);
}
}
}
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
<!-- 千万不能忘记添加访问网络的权限 -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="swu.xl.requesthtml">

<!--允许程序访问网络连接权限-->
<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:icon="@mipmap/view"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

2. 运行结果

四. 下载文件的小例子

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
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
public class MainActivity extends AppCompatActivity {

private Button download;
private ProgressBar progressBar;
private TextView progressText;
private EditText editText;
private DownloadFileTask downloadFileTask;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

editText = findViewById(R.id.edit_url);
download = findViewById(R.id.download);
progressBar = findViewById(R.id.progress_bar);
progressText = findViewById(R.id.progress_text);

download.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
downloadFileTask = new DownloadFileTask();
downloadFileTask.execute(editText.getText().toString());
v.setClickable(false);
}
});
}

@Override
protected void onDestroy() {
super.onDestroy();

downloadFileTask.cancel(false);
}

//下载文件的异步类
private class DownloadFileTask extends AsyncTask<String,Integer,String> {
//任务开始前-主线程
@Override
protected void onPreExecute() {
super.onPreExecute();

//下载前初始化
progressBar.setProgress(0);
progressText.setText("开始下载");
}

//任务开始-工作线程
@Override
protected String doInBackground(String... strings) {

try {
//获得URL
URL url = new URL(strings[0]);
//打开连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//连接
connection.connect();
//获取文件的总长度
int contentLength = connection.getContentLength();
//获得响应流
InputStream is = connection.getInputStream();

//获取该应用的外部存储
File externalFilesDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);

//获取输出流
File file = new File(externalFilesDir, "text.apk");
OutputStream os = new FileOutputStream(file);

//写入
int downloadSize = 0;
int len;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes)) != -1){
os.write(bytes,0,len);

downloadSize += len;
int progress = (int) ((downloadSize / (contentLength * 1.0)) * 100);
publishProgress(progress);
System.out.println("下载进度:"+progress);
//System.out.println("已经下载的大小:"+downloadSize+" "+"总的大小:"+contentLength);
}

//关闭
os.flush();
is.close();
os.close();

return is.toString();

} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

return null;
}

//回调进度-主线程
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);

progressBar.setProgress(values[0]);
progressText.setText("下载进度:"+values[0]+"%");
}

//执行完毕-主线程
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
progressBar.setProgress(100);
progressText.setText("下载完毕");
}
}
}

2. 运行结果

下载好之后,去手机内存中找到下载的文件,发现的确下载成功。

3. 源码

DownloadFile

参考文章

AsyncTask

Android 多线程:手把手教你使用AsyncTask