网络请求是互联网开发的基石,单机的应用终究不能发挥互联网的优势,互联网+任何东西都有不小的发展潜力。在Android开发领域,基本的网络连接主要涉及到数据的上传、下载与网页浏览,而其中数据的上传和下载是APP和服务器交互的基石,目前主要是通过HTTP协议上传和下载文本、XML、Json、图片、音视频文件,基本实现方式主要涉及HttpURLConnection和HttpClient。
本文已收录至☞Android学习路线_梳理
上一篇☞Android学习路线_入门篇(十)数据持久化之数据库
1 网络请求基本知识
1.1 Http协议
实际开发中我们和服务器交互一般用的都是基于Http
协议的通信,所以学好Http协议是非常重要的,当然,我们不用过于考究一些细节的东西,有个大体的了解即可,都是一些概念性的东西。
Http
全称hypertext transfer protocol
(超文本传输协议),TCP/IP
协议的一个应用层协议,用于定义客户端与服务器之间交换数据的过程。客户端连上服务器后,若想获得服务器中的某个资源,需遵守一定的通讯格式,HTTP
协议用于定义客户端与服务器通讯的格式。
Http操作的流程:
- 客户端与服务器建立连接
- 客户端发送请求给服务器,请求的格式为:统一资源标识符(URL) + 协议版本号(一般是1.1) + MIME信息(多个消息头) + 一个空行
- 服务器收到请求后,给予相应的返回信息,返回格式为:协议版本号 + 状态行(处理结果) + 多个信息头 + 空行 + 实体内容(比如返回的XML)
- 客户端接收服务器返回信息,显示出来,然后与服务端断开连接;当然如果中途某步发生错误的话,错误信息会返回到客户端,比如:经典的404错误
1.2 HTTP请求的方式
实际开发中我们用得较多的方式是Get
和Post
,但是实际开发可能还会用到其他请求方式,所有的请求方式如下:
Get
:请求获取Request-URI
所标识的资源POST
:在Request-URI
所标识的资源后附加新的数据HEAD
请求获取由Request-URI
所标识的资源的响应信息报头PUT
:请求服务器存储一个资源,并用Request-URI
作为其标识DELETE
:请求服务器删除Request-URI
所标识的资源TRACE
:请求服务器回送收到的请求信息,主要用于测试或诊断CONNECT
:保留将来使用OPTIONS
:请求查询服务器的性能,或者查询与资源相关的选项
用得最多的Get
和Post
对比如下:
GET
:在请求的URL地址后以?的形式带上交给服务器的数据,多个数据之间以&进行分隔, 但数据容量通常不能超过2K,比如:http://xxx?username=…&pawd=…这种就是GETPOST
: 这个则可以在请求的实体内容中向服务器发送数据,传输没有数量限制- 另外要说一点,
Get
和Post
都是发送数据的,只是发送机制不一样,不要相信网上说的 “Get
获得服务器数据,Post
向服务器发送数据”。另外Get
发送数据用的明文,安全性非常低,Post
发送数据的安全性较高,但是Get
执行效率却比Post
方法好,一般查询的时候我们用Get
,数据增删改的时候用Post
!
1.3 HTTP请求头
HTTP请求头信息对照表:
Header | 解释 | 示例 |
---|---|---|
Accept | 指定客户端能够接收的内容类型 | Accept: text/plain, text/html |
Accept-Charset | 浏览器可以接受的字符编码集。 | Accept-Charset: iso-8859-5 |
Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型。 | Accept-Encoding: compress, gzip |
Accept-Language | 浏览器可接受的语言 | Accept-Language: en,zh |
Accept-Ranges | 可以请求网页实体的一个或者多个子范围字段 | Accept-Ranges: bytes |
Authorization | HTTP授权的授权证书 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Cache-Control | 指定请求和响应遵循的缓存机制 | Cache-Control: no-cache |
Connection | 表示是否需要持久连接。 | (HTTP 1.1默认进行持久连接) |
Cookie | HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 | Cookie: $Version=1; Skin=new; |
Content-Length | 请求的内容长度 | Content-Length: 348 |
Content-Type | 请求的与实体对应的MIME信息 | Content-Type: application/x-www-form-urlencoded |
Date | 请求发送的日期和时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
Expect | 请求的特定的服务器行为 | Expect: 100-continue |
From | 发出请求的用户的Email | From: user@email.com |
Host | 指定请求的服务器的域名和端口号 | Host: www.zcmhi.com |
If-Match | 只有请求内容与实体相匹配才有效 | If-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Modified-Since | 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 | If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
If-None-Match | 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 | If-None-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Range | 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag | If-Range: “737060cd8c284d8af7ad3082f209582d” |
If-Unmodified-Since | 只在实体在指定时间之后未被修改才请求成功 | If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
Max-Forwards | 限制信息通过代理和网关传送的时间 | Max-Forwards: 10 |
Pragma | 用来包含实现特定的指令 | Pragma: no-cache |
Proxy-Authorization | 连接到代理的授权证书 | Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Range | 只请求实体的一部分,指定范围 | Range: bytes=500-999 |
Referer | 先前网页的地址,当前请求网页紧随其后,即来路 | Referer: http://blog.csdn.net/coder_pig |
TE | 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 | TE: trailers,deflate;q=0.5 |
Upgrade | 向服务器指定某种传输协议以便服务器进行转换(如果支持) | Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 |
User-Agent | User-Agent的内容包含发出请求的用户信息 | User-Agent: Mozilla/5.0 (Linux; X11) |
Via | 通知中间网关或代理服务器地址,通信协议 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 关于消息实体的警告信息 | Warn: 199 Miscellaneous warning |
1.4 HTTP响应头
HTTP响应头信息对照表:
Header | 解释 | 示例 |
---|---|---|
Accept-Ranges | 表明服务器是否支持指定范围请求及哪种类型的分段请求 | Accept-Ranges: bytes |
Age | 从原始服务器到代理缓存形成的估算时间(以秒计,非负) | Age: 12 |
Allow | 对某网络资源的有效的请求行为,不允许则返回405 | Allow: GET, HEAD |
Cache-Control | 告诉所有的缓存机制是否可以缓存及哪种类型 | Cache-Control: no-cache |
Content-Encoding | web服务器支持的返回内容压缩编码类型 | Content-Encoding: gzip |
Content-Language | 响应体的语言 | Content-Language: en,zh |
Content-Length | 响应体的长度 | Content-Length: 348 |
Content-Location | 请求资源可替代的备用的另一地址 | Content-Location: /index.htm |
Content-MD5 | 返回资源的MD5校验值 | Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== |
Content-Range | 在整个返回体中本部分的字节位置 | Content-Range: bytes 21010-47021/47022 |
Content-Type | 返回内容的MIME类型 | Content-Type: text/html; charset=utf-8 |
Date | 原始服务器消息发出的时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
ETag | 请求变量的实体标签的当前值 | ETag: “737060cd8c284d8af7ad3082f209582d” |
Expires | 响应过期的日期和时间 | Expires: Thu, 01 Dec 2010 16:00:00 GMT |
Last-Modified | 请求资源的最后修改时间 | Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT |
Location | 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 | Location: http://blog.csdn.net/coder_pig |
Pragma | 包括实现特定的指令,它可应用到响应链上的任何接收方 | Pragma: no-cache |
Proxy-Authenticate | 它指出认证方案和可应用到代理的该URL上的参数 | Proxy-Authenticate: Basic |
2 HttpURLConnection的使用
HttpURLConnection
是一种多用途、轻量极的HTTP
客户端,使用它来进行HTTP
操作可以适用于大多数的应用程序。 虽然HttpURLConnection
的API提供的比较简单,但是同时这也使得我们可以更加容易地去使 用和扩展它。继承至URLConnection
,抽象类,无法直接实例化对象。通过调用openCollection()
方法获得对象实例,默认是带gzip压缩的。
HttpUrlConnection
是JDK
里提供的联网API,我们知道Android SDK
是基于Java
的,所以当然优先考虑HttpUrlConnection
这种最原始最基本的API,其实大多数开源的联网框架基本上也是基于JDK
的HttpUrlConnection
进行的封装罢了。
2.1 初始化
使用之前,先在AndroidManifest中加上联网权限:
<uses-permission android:name="android.permission.INTERNET" />
- 将访问的路径转换成URL:
URL url = new URL(path);
- 通过URL获取连接:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- 设置连接超时时间:
conn.setConnectTimeout(5000); // 设置超时时间为5000毫秒,即5秒
- 设置请求头的信息:
// 根据具体情境设置请求头,可参考 1.3 HTTP请求头
conn.setRequestProperty(Content-Type, application/x-www-form-urlencoded);
- 封装输入流转换成字符串方法:
public String streamToString(InputStream is) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
baos.close();
is.close();
byte[] byteArray = baos.toByteArray();
return new String(byteArray);
} catch (Exception e) {
Log.e(tag, e.toString());
return null;
}
}
2.2 Get请求
- 设置请求方式:
conn.setRequestMethod(GET);
- 获取响应码:
int code = conn.getResponseCode();
- 请求码200,表明请求成功,获取返回内容的输入流:
if (code == 200) {
InputStream is = conn.getInputStream();
}
- 将输入流转换成目标格式:
// 将输入流转换成字符串
String result = streamToString(is);
2.3 Post请求
- 设置请求方式:
conn.setRequestMethod(POST);
- 添加请求数据:
try {
String data = "baseData";
conn.setRequestProperty(Content-Length, data.length() + "");
// POST方式,其实就是把数据写给服务器
conn.setDoOutput(true); // 设置可输出流
OutputStream os = conn.getOutputStream(); // 获取输出流
os.write(data.getBytes()); // 将数据写给服务器
} catch (Exception e) {
e.printStackTrace();
return 网络访问失败;
}
- 获取响应码:
int code = conn.getResponseCode();
- 请求码200,表明请求成功,获取返回内容的输入流:
if (code == 200) {
InputStream is = conn.getInputStream();
}
- 将输入流转换成目标格式:
// 将输入流转换成字符串
String result = streamToString(is);
3 HttpClient的使用
HttpClient
是开源组织Apache
提供的Java请求网络框架,其最早是为了方便Java服务器开发而诞生的,是对JDK中的HttpUrlConnection
各API进行了封装和简化,提高了性能并且降低了调用API的繁琐。
Android因此也引进了这个联网框架,我们再不需要导入任何jar或者类库就可以直接使用,值得注意的是Android官方已经宣布不建议使用HttpClient
了,开发的时候尽量少用吧。尽管被Google 弃用了,但是我们我们平时也可以拿HttpClient
来抓下包。HttpClient
用于接收/发送Http
请求/响应,但不缓存服务器响应,不执行HTML页面潜入的JS代码,不会对页面内容进行任何解析,处理。
3.1 Get请求
- 创建
HttpClient
对象:
HttpClient client = new DefaultHttpClient();
- 创建
HttpGet
对象,指定请求地址(带参数);
HttpGet httpGet = new HttpGet(path);
- 使用
HttpClient
的execute
(),方法执行HttpGet
请求,得到HttpResponse
对象;
HttpResponse response = client.execute(httpGet);
- 调用
HttpResponse
的getStatusLine().getStatusCode()
方法得到响应码;
int code = response.getStatusLine().getStatusCode();
- 调用的
HttpResponse
的getEntity().getContent()
得到输入流,获取服务端写回的数据。
if (code == 200) {
InputStream is = response.getEntity().getContent(); // 获取实体内容
String result = streamToString(is); // 字节流转字符串
}
3.2 Post请求
- 创建
HttpClient
对象;
HttpClient client = new DefaultHttpClient();
- 创建
HttpPost
对象,指定请求地址;
HttpPost httpPost = new HttpPost(path);
- 创建
List
,用来装载参数;
List<namevaluepair> parameters = new ArrayList<namevaluepair>();
parameters.add(new BasicNameValuePair(data, "baseData"));
- 调用
HttpPost
对象的setEntity()
方法,装入一个UrlEncodedFormEntity
对象,携带之前封装好的参数;
httpPost.setEntity(new UrlEncodedFormEntity(parameters, UTF-8));
- 使用
HttpClient
的execute()
方法执行HttpPost
请求,得到HttpResponse
对象;
HttpResponse response = client.execute(httpPost);
- 调用
HttpResponse
的getStatusLine().getStatusCode()
方法得到响应码;
int code = response.getStatusLine().getStatusCode();
- 调用的
HttpResponse
的getEntity().getContent()
得到输入流,获取服务端写回的数据;
if (code == 200) {
InputStream is = response.getEntity().getContent();
String result = streamToString(is);
}
4 数据解析
上面的请求中最后获得的数据格式都是字符串,日常开发中不可能只有纯文本数据,常见的其他数据主要有XML、JSON、图片、音视频文件。其实图片和音视频文件都属于文件,只需要将响应中获取的输入流写入目标格式的文件就完成了。XML和JSON属于特殊的文本数据,通常需要经过再次解析才能使用。
4.1 XML数据解析
常见的XML解析方式有SAX、DOM、PULL三种:
在Android系统中内置了PULL解析器,常用的SharedPreference就是XML格式的数据并且通过PULL完成数据解析,这里就不对SAX和DOM进行详细介绍了,主要介绍一下PULL解析器的使用。
// 首先需要一个实体类Person
class Person{
private String id;
private String name;
private String age;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getAge() { return age; }
public void setAge(String age) { this.age = age; }
}
// 将XML数据转换成Person列表
public static ArrayList<Person> getPersons(InputStream xml)throws Exception{
ArrayList<Person> persons = null;
Person person = null;
// 创建一个xml解析的工厂
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
// 获得xml解析类的引用
XmlPullParser parser = factory.newPullParser();
parser.setInput(xml, "UTF-8");
// 获得事件的类型
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
persons = new ArrayList<Person>();
break;
case XmlPullParser.START_TAG:
if ("person".equals(parser.getName())) {
person = new Person();
// 取出属性值
int id = Integer.parseInt(parser.getAttributeValue(0));
person.setId(id);
} else if ("name".equals(parser.getName())) {
String name = parser.nextText();// 获取该节点的内容
person.setName(name);
} else if ("age".equals(parser.getName())) {
int age = Integer.parseInt(parser.nextText());
person.setAge(age);
}
break;
case XmlPullParser.END_TAG:
if ("person".equals(parser.getName())) {
persons.add(person);
person = null;
}
break;
}
eventType = parser.next();
}
return persons;
}
// 将Person列表存为XML文件
public static void save(List<Person> persons, OutputStream out) throws Exception {
XmlSerializer serializer = Xml.newSerializer();
serializer.setOutput(out, "UTF-8");
serializer.startDocument("UTF-8", true);
serializer.startTag(null, "persons");
for (Person p : persons) {
serializer.startTag(null, "person");
serializer.attribute(null, "id", p.getId() + "");
serializer.startTag(null, "name");
serializer.text(p.getName());
serializer.endTag(null, "name");
serializer.startTag(null, "age");
serializer.text(p.getAge() + "");
serializer.endTag(null, "age");
serializer.endTag(null, "person");
}
serializer.endTag(null, "persons");
serializer.endDocument();
out.flush();
out.close();
}
4.2 JSON数据解析
XML数据已经很少用于网络请求的数据了,现在主流的是JSON数据,这里介绍一下原生的JSONObject和JSONArray,第三方的库感兴趣的可以自行了解,比较常用的有Gson、Fastjson、Jackson。
还是基于4.1中的实体类Person进行分析,Json数据的基本格式示例:
[
{ "id":"1","name":"AAA","age":"18" },
{ "id":"2","name":"BBB","age":"18" },
{ "id":"3","name":"CCC","age":"18" }
]
JSONObject类主要用来解析一个Json数据单位,常用的方法有:
- JSONObject(String json);将Json字符串解析成Json对象;
- getXxx(String name) ;根据name在json对象中得到相应的value值;
JSONObject类主要用来解析一组Json数据,得到一个JSONObject数组,常用的方法有:
- JSONArray(String json);将json字符串解析成JSONObject数组;
- int length();得到JSONObject数组中元素的个数;
简单Json数据解析使用示例:
private void parseEasyJson(String json){
persons = new ArrayList<Person>();
try{
JSONArray jsonArray = new JSONArray(json);
for(int i = 0;i < jsonArray.length();i++){
JSONObject jsonObject = (JSONObject) jsonArray.get(i);
Person person = new Person();
person.setId(i+"");
person.setName(jsonObject.getString("name"));
person.setAge(jsonObject.getString("age"));
persons.add(person);
}
}catch (Exception e){e.printStackTrace();}
}
完毕
今天的分享就到这里,文章多有不足,各位小伙伴有什么想法可以直接评论或是私信,要是对你有所帮助就给我一个赞吧,喜欢我的小伙伴可以关注我哦~
本文已收录至☞Android学习路线_梳理
上一篇☞Android学习路线_入门篇(十)数据持久化之数据库
支持我的小伙伴们可以微信搜索“Android思维库”,或者微信扫描下方二维码,关注我的公众号,每天都会推送新知识~