Android开发基础9-网络技术

本篇文章主要介绍以几下个知识点:

  • 使用 HTTP 协议访问网络:使用 HttpURLConnection 和 OKHttp;
  • 解析 XML 格式数据:Pull 和 SAX 解析;
  • 解析 JSON 数据: JSONObject 和 GSON 解析。

9.1 使用 HTTP 协议访问网络

  HTTP 协议,其工作原理很简单:客户端向服务器发出一条 HTTP 请求,服务器收到请求后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理。

9.1.1 使用 HttpURLConnection

  下面学习 HttpURLConnection 的用法,其请求步骤代码如下:

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
/**
* HttpURLConnection 发送请求
*/
private void sendRequestWithHttpURLConnection() {
// 开启线程来发送网络请求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try{
URL url = new URL("http://www.baidu.com");
// 1. 获取 HttpURLConnection 实例
connection = (HttpURLConnection) url.openConnection();
// 2. 设置请求方法
connection.setRequestMethod("GET");
// 3. 自由定制,如设置连接超时、读取超时等
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
// 4. 获取服务器返回的输入流
InputStream in = connection.getInputStream();
// 下面对获取到的输入流进行读取
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine())!= null){
response.append(line);
}
showResponse(response.toString());// 显示请求结果
}catch (Exception e){
e.printStackTrace();
}finally {
if (reader != null){
try{
reader.close();
}catch (IOException e){
e.printStackTrace();
}
}
if (connection != null){
// 5.把 HTTP 连接关掉
connection.disconnect();
}
}
}
}).start();
}

  别忘了声明网络权限:

1
<uses-permission android:name="android.permission.INTERNET" />

  若是想要提交数据给服务器只需把请求方法改为 POST,并在获取输入流之前把要提交的数据写出即可。注意每条数据要以键值对的形式存在,数据与数据之间用 “&” 隔开,比如向服务器提交用户名和密码可写成:

1
2
3
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");

9.1.2 使用 OKHttp

  接下来学习下网络请求开源项目 OKHttp,其项目主页地址是:https://github.com/square/okhttp

  在使用 OKHttp 前,需要在项目中添加 OKHttp 库的依赖,如下:

1
compile 'com.squareup.okhttp3:okhttp:3.5.0'

  下面学习 OKHttp 请求步骤,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* OKHttp 发送请求
*/
private void sendRequestWithOKHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try{
// 1. 创建 OkHttpClient 实例
OkHttpClient client = new OkHttpClient();
// 2. 创建 Request 对象
Request request = new Request.Builder().url("http://www.baidu.com").build();
// 3. 调用 OkHttpClient 的 newCall() 方法来创建 Call 对象
Response response = client.newCall(request).execute();
// 4. 获取返回的内容
String responseData = response.body().string();
showResponse(responseData);// 显示请求结果
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}

  相比 HttpURLConnection,OKHttp 简单易用,若是发起一条 POST 请求,会比 GET 请求稍微复杂点,需要构建一个 RequestBody 对象来存放待提交的参数:

1
2
3
4
RequestBody requestBody = new FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build();

  然后在 Request.Builder 中调用一下 post() 方法,并将 RequestBody 对象传入:

1
2
3
4
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(RequestBody)
.build();

9.1.3 网络编程的最佳实践

  在实际开发中,我们通常将这些通用的网络操作提取到一个公共类里,接下来就简单封装下网络操作。

  首先针对 HttpURLConnection 定义一个回调接口:

1
2
3
4
public interface HttpCallbackListener {
void onFinish(String response);// 请求成功时调用
void onError(Exception e);// 请求失败时调用
}

  接着编写工具类 HttpUtil:

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
public class HttpUtil {

/**
* 用 HttpURLConnection 发送请求
* @param address
* @param listener
*/
public static void sendHttpRequest(final String address,final HttpCallbackListener listener){
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try{
URL url = new URL(address);
// 1. 获取 HttpURLConnection 实例
connection = (HttpURLConnection) url.openConnection();
// 2. 设置请求方法
connection.setRequestMethod("GET");
// 3. 自由定制,如设置连接超时、读取超时等
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
// 4. 获取服务器返回的输入流
InputStream in = connection.getInputStream();
// 下面对获取到的输入流进行读取
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine())!= null){
response.append(line);
}
if (listener != null){
// 回调 onFinish() 方法
listener.onFinish(response.toString());
}
}catch (Exception e){
if (listener != null){
// 回调 onError() 方法
listener.onError(e);
}
}finally {
if (connection != null){
// 5.把 HTTP 连接关掉
connection.disconnect();
}
}

}
}).start();
}

/**
* 用 OKHttp 发送请求
* @param address
* @param callback
*/
public static void sendOKHttpRequest(String address, Callback callback){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
}
}

  这时候用 HttpURLConnection 发送请求就可以写成:

1
2
3
4
5
6
7
8
9
10
11
HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
// 在这里根据返回内容执行具体的逻辑
}

@Override
public void onError(Exception e) {
// 在这里对异常情况进行处理
}
});

  用 OKHttp 发送请求就可以写成:

1
2
3
4
5
6
7
8
9
10
11
12
HttpUtil.sendOKHttpRequest("http://www.baidu.com", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 在这里对异常情况进行处理
}

@Override
public void onResponse(Call call, Response response) throws IOException {
// 得到服务器返回的具体内容
String responseData = response.body().string();
}
});

  另外需要注意的是,不管是使用 HttpURLConnection 还是 OKHttp,最终回调接口都还是在子线程中运行的。

  下面举个例子巩固下,在布局中放置 Button 用于发送 HTTP 请求,放置一个 TextView 用于显示服务器返回的数据,主要代码如下:

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
public class HttpActivity extends AppCompatActivity implements View.OnClickListener {

private Button send_url_request,send_okHttp_request,clear_content;
private TextView response_text;

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

response_text = (TextView) findViewById(R.id.response_text);

send_url_request = (Button) findViewById(R.id.send_url_request);
send_okHttp_request = (Button) findViewById(R.id.send_okHttp_request);
clear_content = (Button) findViewById(R.id.clear_content);
send_url_request.setOnClickListener(this);
send_okHttp_request.setOnClickListener(this);
clear_content.setOnClickListener(this);

}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.send_url_request:
sendRequestWithHttpURLConnection();

case R.id.send_okHttp_request:
sendRequestWithOKHttp();

case R.id.clear_content:
showResponse(""); //清空数据
}
}

/**
* OKHttp 发送请求
*/
private void sendRequestWithOKHttp() {
HttpUtil.sendOKHttpRequest("http://www.baidu.com", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 在这里对异常情况进行处理
}

@Override
public void onResponse(Call call, Response response) throws IOException {
// 得到服务器返回的具体内容
String responseData = response.body().string();
showResponse(responseData);
}
});
}

/**
* HttpURLConnection 发送请求
*/
private void sendRequestWithHttpURLConnection() {
HttpUtil.sendHttpRequest("http://www.baidu.com", new HttpCallbackListener() {
@Override
public void onFinish(String response) {
// 在这里根据返回内容执行具体的逻辑
showResponse(response);
}

@Override
public void onError(Exception e) {
// 在这里对异常情况进行处理
}
});
}

/**
* 显示请求结果
* @param response
*/
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在这里进行UI 操作,将结果显示到界面上
response_text.setText(response);
}
});
}
}

  运行效果如下:

9.2 解析 XML 格式数据

  在网络上传输数据时最常用的格式有两种:XML 和 JSON。本节来学习下如何解析 XML 格式的数据。

  解析 XML 格式的数据有多种方式,这里主要介绍 Pull 解析和 SAX 解析。解析前先来看看等下要解析的 XML 文本:

9.2.1 Pull 解析方式

  Pull 解析整个过程比较简单,具体看代码注释:

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
/**
* pull 解析
* @param xmlData 要解析的xml数据
*/
private void parseXMLWithPull(String xmlData) {
try {
// 1. 获取 XmlPullParserFactory 实例
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
// 2. 借助 XmlPullParserFactory 实例得到 XmlPullParser 对象
XmlPullParser xmlPullParser = factory.newPullParser();
// 3. 调用 setInput() 方法设置xml数据
xmlPullParser.setInput(new StringReader(xmlData));
// 4. 获取当前的解析事件
int eventType = xmlPullParser.getEventType();
String id = "";
String name = "";
String sex = "";
// 5. 通过 while 循环不断地进行解析
while (eventType != XmlPullParser.END_DOCUMENT){
String nodeName = xmlPullParser.getName();
switch (eventType){
// 开始解析某个节点
case XmlPullParser.START_TAG:
if ("id".equals(nodeName)){
id = xmlPullParser.nextText();
}else if ("name".equals(nodeName)){
name = xmlPullParser.nextText();
}else if ("sex".equals(nodeName)){
sex = xmlPullParser.nextText();
}
break;

// 完成解析某个节点
case XmlPullParser.END_TAG:
if ("student".equals(nodeName)){
Log.d("pull解析:", "id is" + id);
Log.d("pull解析:", "name is" + name);
Log.d("pull解析:", "sex is" + sex);
}
break;

default:
break;
}
// 获取下一个解析事件
eventType = xmlPullParser.next();
}
}catch (Exception e){
e.printStackTrace();
}
}

9.2.2 SAX 解析方式

  SAX 解析的用法比 Pull 解析要复杂些,但在语义方面会更加清楚。

  用 SAX 解析需要建一个类继承 DefaultHandler,并重写父类的5个方法。为实现上面同样的功能,新建一个 ContentHandler 类,如下所示:

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
public class ContentHandler extends DefaultHandler {

private String nodeName;
private StringBuilder id;
private StringBuilder name;
private StringBuilder sex;

/**
* 开始 XML 解析时调用
* @throws SAXException
*/
@Override
public void startDocument() throws SAXException {
id = new StringBuilder();
name = new StringBuilder();
sex = new StringBuilder();
}

/**
* 开始解析某个节点时调用
* @param uri
* @param localName
* @param qName
* @param attributes
* @throws SAXException
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// 记录当前节点名
nodeName = localName;
}

/**
* 获取节点中的内容时调用
* @param ch
* @param start
* @param length
* @throws SAXException
*/
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 根据当前节点名判断将内容添加到哪一个 StringBuilder 对象中
if ("id".equals(nodeName)){
id.append(ch,start,length);
}else if ("name".equals(nodeName)){
name.append(ch,start,length);
}else if ("sex".equals(nodeName)){
sex.append(ch,start,length);
}
}

/**
* 完成解析某个节点时调用
* @param uri
* @param localName
* @param qName
* @throws SAXException
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("student".equals(localName)){
Log.d("sax解析:", "id is" + id.toString().trim());
Log.d("sax解析:", "name is" + name.toString().trim());
Log.d("sax解析:", "sex is" + sex.toString().trim());
// 最后要将 StringBuilder 清空掉
id.setLength(0);
name.setLength(0);
sex.setLength(0);
}
}


/**
* 完成整个 XML 解析时调用
* @throws SAXException
*/
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}

  接下来就非常简单了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* sax 解析
* @param xmlData
*/
private void parseXMLWithSAX(String xmlData){
try {
// 创建 SAXParserFactory 对象
SAXParserFactory factory = SAXParserFactory.newInstance();
// 获取 XMLReader 对象
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
ContentHandler handler = new ContentHandler();
// 将 ContentHandler 的实例设置到 XMLReader 中
xmlReader.setContentHandler(handler);
// 开始执行解析
xmlReader.parse(new InputSource(new StringReader(xmlData)));
}catch (Exception e){
e.printStackTrace();
}
}

9.2.3 举个例子实在点

  下面在布局中放置两个按钮,分别进行pull解析和sax解析:

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
public class ParseXMLActivity extends AppCompatActivity implements View.OnClickListener {

private Button btn_pull,btn_sax;

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

btn_pull = (Button) findViewById(R.id.btn_pull);
btn_sax = (Button) findViewById(R.id.btn_sax);

btn_pull.setOnClickListener(this);
btn_sax.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_pull:
HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.xml", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//ToastUtils.showShort("请求失败");
}

@Override
public void onResponse(Call call, Response response) throws IOException {
String responseData = response.body().string();
parseXMLWithPull(responseData); // pull 解析
}
});
break;

case R.id.btn_sax:
HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.xml", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//ToastUtils.showShort("请求失败");
}

@Override
public void onResponse(Call call, Response response) throws IOException {
String responseData = response.body().string();
parseXMLWithSAX(responseData); // sax 解析
}
});
break;
}
}

/**
* pull 解析
* @param xmlData 要解析的xml数据
*/
private void parseXMLWithPull(String xmlData) { . . . }

/**
* sax 解析
* @param xmlData
*/
private void parseXMLWithSAX(String xmlData){ . . . }
}

  运行程序,打印的日志分别如下:


  可以看到,已经将 XML 数据成功解析出来了。

9.3 解析 JSON 数据

  类似的,解析 JSON 格式的数据有多种方式,这里主要介绍官方提供的 JSONObject 和谷歌的开源库 GSON 来解析。解析前先来看看等下要解析的 JSON 文本:

9.3.1 使用 JSONObject

  使用 JSONObject 解析上面内容比较简单,具体看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 用 JSONObject 解析
* @param jsonData 需要解析的数据
*/
private void parseJSONWithJSONObject(String jsonData) {
try {
// 把需要解析的数据传入到 JSONArray 对象中
JSONArray jsonArray = new JSONArray(jsonData);
for (int i = 0;i < jsonArray.length();i++){
JSONObject jsonObject = jsonArray.getJSONObject(i);
String id = jsonObject.getString("id");
String name = jsonObject.getString("name");
String sex = jsonObject.getString("sex");
Log.d("JSONObject解析", "id is "+id);
Log.d("JSONObject解析", "name is "+name);
Log.d("JSONObject解析", "sex is "+sex);
}
}catch (Exception e){
e.printStackTrace();
}
}

9.3.2 使用 GSON

  接下来学习下开源库 GSON,其项目主页地址是:https://github.com/google/gson

  在使用 GSON 前,需要在项目中添加 GSON 库的依赖,如下:

1
compile 'com.google.code.gson:gson:2.8.0'

  GSON 可以将一段 JSON 格式的字符串自动映射成一个对象,从而不需要手动去编写代码进行解析了。

  比如解析一段 JSON 格式数据:

1
{"name":"Tom","age":20}

  就可以定义一个 Person 类,并加入 name 和 age 两字段,然后只需调用如下代码就可以将 JSON 数据自动解析成一个 Person 对象:

1
2
Gson gson = new Gson();
Person person = gson.fromJson(jsonData,Person.class);

  若解析一段 JSON 数组会麻烦些,需要借助 TypeToken 把期望解析成的数据类型传入到 fromJson() 方法中:

1
List<Person> people = gson.fromJson(jsonData,new TypeToken<List<Person>>(){}.getType());

  GSON 的基本用法就是这样。下面来解析上面的 JSON 文本,首先新增一个 Student 类:

1
2
3
4
5
6
7
8
9
public class Student {

private String id;
private String name;
private String sex;

// Getter and Setter
. . .
}

  接下来就非常简单了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 用 GSON 解析
* @param jsonData
*/
private void parseJSONWithGSON(String jsonData){
Gson gson = new Gson();
List<Student>studentList = gson.fromJson(jsonData,new TypeToken<List<Student>>(){}.getType());
for (Student student:studentList){
Log.d("GSON解析", "id is "+student.getId());
Log.d("GSON解析", "name is "+student.getName());
Log.d("GSON解析", "sex is "+student.getSex());
}
}

9.3.3 举个例子实在点

  下面在布局中放置两个按钮,分别用 JSONObject 和 GSON 进行 json 解析:

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
public class ParseJSONActivity extends AppCompatActivity implements View.OnClickListener {

private Button btn_object,btn_gson;

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

btn_object = (Button) findViewById(R.id.btn_object);
btn_gson = (Button) findViewById(R.id.btn_gson);

btn_object.setOnClickListener(this);
btn_gson.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_object:
HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.json", new Callback() {
@Override
public void onFailure(Call call, IOException e) {}

@Override
public void onResponse(Call call, Response response) throws IOException {
String responseData = response.body().string();
parseJSONWithJSONObject(responseData); // 用 JSONObject 解析
}
});
break;

case R.id.btn_gson:
HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.json", new Callback() {
@Override
public void onFailure(Call call, IOException e) {}

@Override
public void onResponse(Call call, Response response) throws IOException {
String responseData = response.body().string();
parseJSONWithGSON(responseData); // 用 GSON 解析
}
});
break;
}

}

/**
* 用 JSONObject 解析
* @param jsonData 需要解析的数据
*/
private void parseJSONWithJSONObject(String jsonData) { . . . }

/**
* 用 GSON 解析
* @param jsonData
*/
private void parseJSONWithGSON(String jsonData){ . . . }
}

  运行程序,打印的日志分别如下:


  关于网络编程先学习到这,下篇文章将进入安卓四大组件之服务的学习。

Jason Xu wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!